c++有效的缓冲文本和有条件地将部分写入文本文件的方法

C++ efficient way to buffer text and conditionally write portions to a text file

本文关键字:文本 文件 方法 缓冲 有效 有条件 c++      更新时间:2023-10-16

我正在构建包含数亿行的CSV文本文件。对record函数的每次调用形成一行文本并将其缓冲到stringstream中。周期性地,根据record函数的输入,缓冲行要么被写入文件,要么被丢弃。我猜大约75%的缓冲行在大多数情况下最终被写入文件。

所以,我真正在做的是形成一堆文本行,决定是扔掉它们还是把它们写进文件,然后一遍又一遍地重复很多次。

下面是我的代码的简化示例。设CONDITION1CONDITION2为简单布尔表达式,包含xyz;它们不需要花费大量的时间来评估。代码确实很慢,我可以看到几个原因:通常使用stringstreams,特别是反复调用stringstream::str()stringstream::str(const string&)

问题:我怎样才能使它更快?

注意:我假设(或知道)使用std::string来保存一堆文本会更快,但我担心使用double变量(如x)构建文本所需的额外转换。(在实际情况中,大约有10个不同的双变量以逗号分隔并在一起。)

std::ofstream outf;
stringstream ss;
// open outf
void record(const double x, const bool y, const int z) {
    ss << x << ", ";
    if(y) ss << "YES, ";
    else  ss << "NO, ";
    ss << z << "n";
    if(CONDITION1) {
        if(CONDITION2)
            outf << ss.str();
        ss.str(std::string());
    }
}

假设这是可能的,那么第一个也是最明显的优化就是在执行任何转换之前检查条件。与此同时,您可以避免从stringstream的内容创建string对象,而只是直接从stringstream的缓冲区复制到ostream的缓冲区。我还可能使用一个向量来处理布尔转换。特别是对于不包括短字符串优化的实现,您还可以通过预先初始化一个空字符串来清除stringstream来节省一些时间:

std::ofstream outf;
stringstream ss;
namespace {
    char const *names[] = { "No, ", "Yes, "};
    std::string clear;
}
// open outf
void record(const double x, const bool y, const int z) {
    if (!(CONDITION1 && CONDITION2))
        return;
    ss << x << ", ";
    ss << names[y];
    ss << z << "n";
    outf << ss.rdbuf();
    ss.str(clear);
}

假设您实际上可以像这样尽早检查条件,那么您可能也可以完全消除stringstream

void record(const double x, const bool y, const int z) {
    if (!(CONDITION1 && CONDITION2))
        return;
    outf << x << ", "
         << names[y]
         << z << "n";
}

老实说,我怀疑这是否会产生巨大的差异,但这些是我在没有看到你真正关心的代码的情况下立即想到的最佳猜测。

Background

有两个基本问题导致经济放缓:

  1. 获取数据
  2. 解析数据

这些通常是占用时间最多的项目。

获取数据

获取数据的最佳过程是保持数据流入内存。这可能意味着从读取大块数据到使用线程保持读取。如果你正在使用硬盘,它们不喜欢停下来。他们有启动和定位部门的开销。可以通过读取大块(每个请求读取更多数据)来减少启动。

解析数据

在搜索分隔字符和将文本表示转换为内部表示时浪费了时间。

固定的字段长度是最快的解析。不需要搜索,数据在固定字符位置。这消除了搜索分隔字符的时间。

同样,固定字段更容易从缓冲区中处理。对于可变长度的记录,记录可能跨越缓冲区的末尾,从而导致执行一些额外的代码。

减少分支

处理器倾向于连续执行指令。当他们碰到分支指令时,他们会有点不安。这意味着它们必须从其他地方获取指令。条件更糟。在条件解决之前,提取机制不能提取(尽管在提取算法上有更多的研究来加速它们)。总之,减少瓶颈区域的分支数量。

概要文件。那就试试这些技巧吧。概要文件。比较"前"answers"后"配置文件来确定增益。

去掉iostreams。甚至不计算文件I/O本身,仅仅构建您的stringstream就已经使您的性能降低了一个数量级以上,而不是磁盘应有的能力。(见这里的证据)。

使用Jerry的提前退出技巧,然后考虑使用普通C字符串处理函数构建缓冲区,如strncat(或"安全"版本,负责缓冲区溢出预防,因此您不必这样做)或snprintf

c++ iostreams几乎可以胜任使用filebuf将准备好的缓冲区传输到磁盘,但是您可能仍然需要对FILE*和/或OS文件访问api进行基准测试。

另外,在将几十条记录传递到磁盘之前,不要害怕将它们缓冲到应用程序级缓冲区中。