我想比较使用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的建议scanf
成char
阵列,而不是getline
成std::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代码,非同步cin
和fgets
方法,以及与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 *
缓冲区和fgets
与cin
字符串不同步的小额权衡是后者可以读取任何长度的行,而前者需要将输入限制为某个有限数。实际上,这对于读取大多数基于行的输入文件来说可能不是问题,因为缓冲区可以设置为非常大的值,有效输入不会超过该值。
这是教育性的。感谢大家的意见和建议。
编辑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
发布于 2018-09-27 09:11:53
我在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秒。
发布于 2018-09-27 09:45:01
getline
,scanf
如果您不关心文件加载时间或加载小文本文件,则可以方便地使用流操作符。但是,如果性能是您关心的,那么您应该将整个文件缓冲到内存中(假设它适合)。
这是一个例子:
//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);
此外,如果您控制文件,请考虑使用平面二进制数据格式而不是文本。它的读写更可靠,因为您不必处理空白的所有歧义。解析时它也更小,速度更快。
https://stackoverflow.com/questions/-100008873
复制相似问题