首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么在C ++中读取stdin的行比Python要慢得多?

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

Stack Overflow用户
提问于 2018-09-27 00:36:54
回答 2查看 0关注 0票数 0

我想比较使用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
EN

Stack Overflow用户

发布于 2018-09-27 09:45:01

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);

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

票数 0
EN
查看全部 2 条回答
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/-100008873

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档