如何优化C ++以逐行读取文件?

时间:2016-02-25 16:32:00

标签: python c++ optimization stream

令我惊讶的是,我注意到以下C ++只是读取一个大文件行,将这些行存储到一个向量中并输出向量大小比它的Python对象运行速度慢。如何优化呢?感谢

#include <fstream>
#include <iostream>
#include <vector>
#include <string>

   int main () {
    std::ifstream infile("longfile.txt");
    std::string line;
    std::vector<std::string> lines;

    while (std::getline(infile, line)) {
        lines.push_back(line);
    }

    std::cout << lines.size() << std::endl;

    return 0;
}

执行命令

$ time ./a.out
1390000

real    0m6.388s
user    0m6.130s
sys 0m0.243s

的Python:

with open('longfile.txt') as f:
    lines = f.readlines()

print len(lines)

执行命令

$ time python test.py
1390000

real    0m1.003s
user    0m0.158s
sys     0m0.146s

2 个答案:

答案 0 :(得分:2)

如果您事先知道必须阅读的行数,则可以预约std::vector对象中的行数。 假设你有10行阅读:

   int main () {
    std::ifstream infile("longfile.txt");
    std::string line;
    std::vector<std::string> lines;

    lines.reserve(10);

    while (std::getline(infile, line)) {
        lines.push_back(line);
    }

    std::cout << lines.size() << std::endl;

    return 0;
}

像这样你的记忆将在你开始上班之前分配。 Becarefull reserve不是resize。 我的意思是lines.reserve(10);将提前分配10 std::string,但lines.empty()仍然是真的。

如果您无法预先知道行数std::list(双链表),或std::forward_list(简单链接列表)将会有所帮助。 每个新元素都将添加到列表中,直到您完成读取文件。 在列表中,无需重新分配内存,每次达到最大容量时必须对std::vector执行的操作。 内存中的重新分配和复制元素在时间上非常昂贵。 使用列表,您至少可以减少解析文件所花费的时间。

使用复制解析文件后,列表到std::vector是一个好主意,因为您已经知道所需的内存大小,连接内存分配的访问速度更快。

无论你选择什么,我都强烈要求改变:

while (std::getline(infile, line)) {
    lines.push_back(line);
}

by:

    while (std::getline(infile, line)) {
        lines.push_back(std::move(line));
    }

默认情况下,对于STL中的大多数容器,复制构造函数或分配运算符的调用将完全复制数据。 std::move阻止此类副本。

您可以使用以下示例轻松检查:

std::string a("Hello");
std::string b(a);

std::cout<<a.size()<<" "<<b.size()<<std::endl;
std::cout<<"the address of a is : "<<a.c_str()<<" "<<b.c_str()<<std::endl;

std::string d(std::move(a));

std::cout<<a.size()<<" "<<d.size()<<std::endl;

std::string e;
std::string f;

e = b;

std::cout<<e.size()<<" "<<b.size()<<std::endl;
std::cout<<"the address of a is : "<<e.c_str()<<" "<<b.c_str()<<std::endl;


f = std::move(b);

std::cout<<f.size()<<" "<<b.size()<<std::endl;

答案 1 :(得分:1)

几乎所有优化问题的答案都是“首先,个人资料”。分析您的C ++应用程序并确定花费的时间。

尽管如此,我还是可以对这里的慢点进行一些有根据的猜测,然后指出这会在分析器中显示出来。

慢速getline()

getline()可能会以缓慢的方式实施。例如,它可能需要一次向运行时询问一个字符,因为一旦它看起来像是换行符,它就需要停止读取。也就是说,它不能在更大的块中请求字节,因为当新行出现在块的中间时,它无法保证“放回”块的其余部分。

运行时几乎肯定要缓冲底层文件读取,因此这不会像每个字符的一个系统调用那样差,但是为文件中的每个字符有效地调用getc的开销仍然很重要。

这会在探查器中显示为花费在getline()上的大量时间 - 特别是在一些getc() - 类似于getline调用的方法。

python实现完全没有这个问题,因为只进行了一次readlines()调用,并且实现知道整个文件将被读取并且可以随意缓冲。

冗余复制

另一个可能的候选人是冗余复制。

第一个运行时进行read()调用,并将文件的块复制到内部缓冲区中。然后getline()实现可能会有一个char[]的内部缓冲区,它会在将字符串传递给string构造函数之前构建字符串,这可能会生成另一个副本(除非运行时为使用内部技巧直接交出缓冲区。)

然后,正如Johnny_S指出的那样,当你将这些字符串推入向量时,可能会有更多副本。

如上所述,这将在向量中显示,因为时间花费在各种副本中,例如在string()构造函数中。

python实现也可以避免大多数这些冗余副本,因为它具有更高级别的问题视图,而不是C ++实现中的分层方法,因此它可能只生成1或2个副本。

解决方案

这里提到的解决方案解决了上述两个问题。要重新实现Python readlines调用,您应该降低一点。以char[]的块为单位读取文件,并直接在缓冲区中查找换行符。从技术上讲,您根本不需要创建string个对象,因为您只输出找到的行数,但如果您确实要创建这些对象,请确保只复制char[]数据一旦进入每个字符串。

您可以使用string (const char* s, size_t n)构造函数执行此操作,直接指向您的字符缓冲区。最后,确保在复制到载体时不要制作另一个副本,正如Johnny_S建议的那样。