为什么在C ++中读取stdin的行比Python要慢得多?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (83)

我想比较使用Python和C ++从stdin读取字符串的读取行,并且看到我的C ++代码比同等的Python代码运行慢一个数量级时感到震惊。由于我的C ++生锈了,我还不是专家Pythonista,请告诉我,如果我做错了什么或者我是否误解了什么。

(TLDR回答:包含声明:cin.sync_with_stdio(false)或者只是fgets改用。

TLDR结果:一直向下滚动到我的问题的底部并查看表格。)

C ++代码:

#include <iostream>
#include <time.h>

using namespace std;

int main() {
    string input_line;
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    while (cin) {
        getline(cin, input_line);
        if (!cin.eof())
            line_count++;
    };

    sec = (int) time(NULL) - start;
    cerr << "Read " << line_count << " lines in " << sec << " seconds.";
    if (sec > 0) {
        lps = line_count / sec;
        cerr << " LPS: " << lps << endl;
    } else
        cerr << endl;
    return 0;
}

// Compiled with:
// g++ -O3 -o readline_test_cpp foo.cpp

Python等价物:

#!/usr/bin/env python
import time
import sys

count = 0
start = time.time()

for line in  sys.stdin:
    count += 1

delta_sec = int(time.time() - start_time)
if delta_sec >= 0:
    lines_per_sec = int(round(count/delta_sec))
    print("Read {0} lines in {1} seconds. LPS: {2}".format(count, delta_sec,
       lines_per_sec))

这是我的结果:

$ cat test_lines | ./readline_test_cpp
Read 5570000 lines in 9 seconds. LPS: 618889

$cat test_lines | ./readline_test.py
Read 5570000 lines in 1 seconds. LPS: 5570000

编辑: 我应该注意到我在Mac OS X v10.6.8(Snow Leopard)和Linux 2.6.32(Red Hat Linux 6.2)下都尝试了这一点。前者是MacBook Pro,后者是一个非常强大的服务器,而不是太过贴切。

编辑2 :( 删除此编辑,因为不再适用)

$ for i in {1..5}; do echo "Test run $i at `date`"; echo -n "CPP:"; cat test_lines | ./readline_test_cpp ; echo -n "Python:"; cat test_lines | ./readline_test.py ; done
Test run 1 at Mon Feb 20 21:29:28 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 2 at Mon Feb 20 21:29:39 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 3 at Mon Feb 20 21:29:50 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 4 at Mon Feb 20 21:30:01 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 5 at Mon Feb 20 21:30:11 EST 2012
CPP:   Read 5570001 lines in 10 seconds. LPS: 557000
Python:Read 5570000 lines in  1 seconds. LPS: 5570000

编辑3:

好吧,我尝试了JN的建议,试着让Python存储行读取:但它对python的速度没有任何影响。

我也尝试过使用的JN的建议scanfchar阵列,而不是getlinestd::string。答对了!这导致Python和C ++的性能相当。(3,333,333 LPS带有我的输入数据,顺便说一下,每行只有三个字段的短行,通常大约20个字符宽,但有时更多)。

码:

char input_a[512];
char input_b[32];
char input_c[512];
while(scanf("%s %s %s\n", input_a, input_b, input_c) != EOF) {
    line_count++;
};

速度:

$ cat test_lines | ./readline_test_cpp2
Read 10000000 lines in 3 seconds. LPS: 3333333
$ cat test_lines | ./readline_test2.py
Read 10000000 lines in 3 seconds. LPS: 3333333

(是的,我跑了好几次。)所以,我想我现在会用scanf而不是getline。但是,我仍然好奇,如果人们认为这样的表现从打std::string/ getline是典型的和合理的。

编辑4(是:最终编辑/解决方案):

添加:

cin.sync_with_stdio(false);

紧接在我原来的while循环之上导致代码运行得比Python快。

新的性能比较(这是在我的2011 MacBook Pro上),使用原始代码,禁用同步的原始代码和原始Python代码,分别在具有20M行文本的文件上。是的,我运行了几次以消除磁盘缓存混乱。

$ /usr/bin/time cat test_lines_double | ./readline_test_cpp
       33.30 real         0.04 user         0.74 sys
Read 20000001 lines in 33 seconds. LPS: 606060
$ /usr/bin/time cat test_lines_double | ./readline_test_cpp1b
        3.79 real         0.01 user         0.50 sys
Read 20000000 lines in 4 seconds. LPS: 5000000
$ /usr/bin/time cat test_lines_double | ./readline_test.py
        6.88 real         0.01 user         0.38 sys
Read 20000000 lines in 6 seconds. LPS: 3333333

感谢@Vaughn Cato的回答!人们可以做出的任何详细说明或者很好的参考,人们可以指出为什么这种同步发生,它意味着什么,什么时候有用,什么时候可以禁用,后代将非常感激。:-)

编辑5 /更好的解决方案:

正如下面的Gandalf The Gray所建议的那样,gets甚至比scanf不同步的cin方法还要快。我也了解到scanf并且gets都是UNSAFE,因为可能存在缓冲区溢出而不应该使用它。所以,我使用fgets了更好的替代方法来编写这个迭代。以下是我的同伴们的相关内容:

char input_line[MAX_LINE];
char *result;

//<snip>

while((result = fgets(input_line, MAX_LINE, stdin )) != NULL)
    line_count++;
if (ferror(stdin))
    perror("Error reading stdin.");

现在,这里是使用更大的文件(100M行; ~3.4 GB)在具有非常快的磁盘的快速服务器上的结果,比较Python代码,非同步cinfgets方法,以及与wc实用程序进行比较。[ scanf版本细分出现故障,我不想对其进行故障排除。]:

$ /usr/bin/time cat temp_big_file | readline_test.py
0.03user 2.04system 0:28.06elapsed 7%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
Read 100000000 lines in 28 seconds. LPS: 3571428

$ /usr/bin/time cat temp_big_file | readline_test_unsync_cin
0.03user 1.64system 0:08.10elapsed 20%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
Read 100000000 lines in 8 seconds. LPS: 12500000

$ /usr/bin/time cat temp_big_file | readline_test_fgets
0.00user 0.93system 0:07.01elapsed 13%CPU (0avgtext+0avgdata 2448maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps
Read 100000000 lines in 7 seconds. LPS: 14285714

$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
100000000


Recap (lines per second):
python:         3,571,428
cin (no sync): 12,500,000
fgets:         14,285,714
wc:            54,644,808

正如你所看到的,fgets更好,但仍然远离wc性能; 我很确定这是因为wc检查每个字符而没有任何内存复制。我怀疑,在这一点上,代码的其他部分将成为瓶颈,所以我不认为优化到那个级别甚至是值得的,即使可能(因为,毕竟,我实际上需要存储读取线在记忆中)。

还要注意,使用char *缓冲区和fgetscin字符串不同步的小额权衡是后者可以读取任何长度的行,而前者需要将输入限制为某个有限数。实际上,这对于读取大多数基于行的输入文件来说可能不是问题,因为缓冲区可以设置为非常大的值,有效输入不会超过该值。

这是教育性的。感谢大家的意见和建议。

编辑6:

正如JF Sebastian在下面的评论中所建议的那样,GNU wc实用程序使用普通的C read()(在safe-read.c包装器中)一次读取块(16k字节)并计算新行。这是基于JF代码的Python等价物(仅显示替换Python for循环的相关代码段:

BUFFER_SIZE = 16384
count = sum(chunk.count('\n') for chunk in iter(partial(sys.stdin.read, BUFFER_SIZE), ''))

这个版本的性能非常快(当然,它仍然比原始的C wc实用程序慢一点):

$ /usr/bin/time cat temp_big_file | readline_test3.py
0.01user 1.16system 0:04.74elapsed 24%CPU (0avgtext+0avgdata 2448maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps
Read 100000000 lines in 4.7275 seconds. LPS: 21152829

同样,对我来说,将C ++ fgets/ cin和第一个python代码wc -l与最后一个Python代码段进行比较有点愚蠢,因为后两个实际上并不存储读取行,而只是计算换行符。尽管如此,探索所有不同的实现并考虑性能影响仍然很有趣。再次感谢!

编辑7:微小的基准附录和回顾

为了完整起见,我想我会使用原始(同步)C ++代码在同一个盒子上更新同一文件的读取速度。同样,这是针对快速磁盘上的100M线路文件。这是完整的表格:

Implementation      Lines per second
python (default)           3,571,428
cin (default/naive)          819,672
cin (no sync)             12,500,000
fgets                     14,285,714
wc (not fair comparison)  54,644,808
提问于
用户回答回答于

getlinescanf如果您不关心文件加载时间或加载小文本文件,则可以方便地使用流操作符。但是,如果性能是您关心的,那么您应该将整个文件缓冲到内存中(假设它适合)。

这是一个例子:

//open file in binary mode
std::fstream file( filename, std::ios::in|::std::ios::binary );
if( !file ) return NULL;

//read the size...
file.seekg(0, std::ios::end);
size_t length = (size_t)file.tellg();
file.seekg(0, std::ios::beg);

//read into memory buffer, then close it.
char *filebuf = new char[length+1];
file.read(filebuf, length);
filebuf[length] = '\0'; //make it null-terminated
file.close();

如果需要,可以在该缓冲区周围包装一个流,以便更方便地访问:

std::istrstream header(&filebuf[0], length);

此外,如果您控制文件,请考虑使用平面二进制数据格式而不是文本。它的读写更可靠,因为您不必处理空白的所有歧义。解析时它也更小,速度更快。

用户回答回答于

我在Mac上使用g ++在计算机上复制了原始结果。

while循环之前将以下语句添加到C ++版本使其与Python版本内联:

std::ios_base::sync_with_stdio(false);
char buffer[1048576];
std::cin.rdbuf()->pubsetbuf(buffer, sizeof(buffer));

sync_with_stdio将速度提高到2秒,设置更大的缓冲区使其降低到1秒。

扫码关注云+社区

领取腾讯云代金券