为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

德玛西亚99 2019-06-21 16:28:35
为什么在C+中从stdin读取行的速度比Python慢得多?我想比较一下使用Python和C+从stdin中读取字符串输入的行,并惊讶地看到我的C+代码运行的速度比等效的Python代码慢一个数量级。由于我的C+生疏了,而且我还不是Pythonsta专家,请告诉我是否做错了什么,或者我是否误解了什么。(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.cppPython等效:#!/usr/bin/env pythonimport timeimport syscount = 0start = time.time()for line in  sys.stdin:    count += 1delta_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_cppRead 5570000 lines in 9 seconds. LPS: 618889$cat test_lines | ./readline_test.pyRead 5570000 lines in 1 seconds. LPS: 5570000我应该注意,我在MacOSXv10.6.8(雪豹)和Linux2.6.32(RedHatLinux6.2)下都尝试过这一点。前者是一个MacBookPro,后者是一个非常强大的服务器,这并不是说这太相关了。
查看完整描述

3 回答

?
小唯快跑啊

TA贡献1863条经验 获得超2个赞

我落后了几年,但是:

在原始文章的“编辑4/5/6”中,您使用的是结构:

$ /usr/bin/time cat big_file | program_to_benchmark

这在几个不同的方面是错误的:

  1. 你实际上是在计时“猫”的执行,而不是你的基准。“time”显示的‘user’和‘sys’CPU使用率是‘cat’,而不是您的基准程序。更糟糕的是,“真实”时间也不一定准确。根据‘cat’和本地操作系统中管道的实现,‘cat’可能会写入最后一个巨大缓冲区,并在读者进程完成工作之前就退出。

  2. 使用‘cat’是不必要的,实际上适得其反;你在添加移动部件。如果您使用的是一个足够老的系统(即使用单个CPU,并且-在某些代计算机中-I/O比CPU快)-仅仅是`cat‘正在运行这一事实就会严重影响结果。您还受输入和输出缓冲以及其他“cat”处理可能做的任何操作的限制。(这可能会给你带来一个“猫的无用途”如果我是兰德尔·施瓦茨。

一个更好的结构是:

$ /usr/bin/time program_to_benchmark < big_file

在这个声明中,它是它打开bigfile,将其作为已经打开的文件描述符传递给您的程序(实际上是“time”,然后作为子进程执行您的程序)。100%的文件读取严格来说是你想要测试的程序的责任。这可以让您真正地阅读它的性能,而不会出现虚假的复杂情况。

我将提到两个可能但实际上是错误的,也可以考虑的“修正”(但我对它们的“编号”不同,因为在最初的文章中,这些都不是错误的):

答:你只需计时你的程序就可以“修复”这个问题:

$ cat big_file | /usr/bin/time program_to_benchmark

B.或根据整个管道的时间安排:

$ /usr/bin/time sh -c 'cat big_file | program_to_benchmark'

这些错误的原因与#2相同:它们仍然不必要地使用“cat”。我提到它们有几个原因:

  • 对于那些对POSIX外壳的I/O重定向设施不太满意的人来说,它们更“自然”。

  • 在某些情况下,“猫”需要(例如:要读取的文件需要某种访问权限,并且您不希望将该权限授予要对其进行基准测试的程序:‘sudo cat/dev/sda区/usr/bin/time my_press_test-no-output’)

  • 在实践中,在现代机器上,在管道中添加的‘cat’可能没有什么实际意义。

但我说的最后一件事有点犹豫。如果我们检查“编辑5”的最后结果-

$ /usr/bin/time cat temp_big_file | wc -l0.01user 1.34system 0:01.83elapsed 74%CPU ...

-这声称“cat”在测试期间消耗了74%的CPU;实际上1.34/1.83约占74%。也许是一连串的:

$ /usr/bin/time wc -l < temp_big_file

剩下的0.49秒就好了!可能不会:这里的`cat‘必须支付从’disk‘(实际上是缓冲区缓存)传输文件的read()系统调用(或等效的),以及管道写入以将它们传递给’wc‘。正确的测试仍然需要执行这些read()调用;只有写到管道和从管道读取的调用才会被保存,而且这些调用应该相当便宜。

不过,我预计您将能够测量“Cat file x WC-l”和“wc-l<file”之间的差异,并发现明显的(2位数的百分比)差异。每一次较慢的测试都将在绝对时间内支付类似的惩罚;然而,这将相当于其较长时间的一小部分。

实际上,我在Linux 3.13(Ubuntu14.04)系统上对1.5G垃圾文件做了一些快速测试,获得了以下结果(这些结果实际上是“3中最好的”结果;当然,在启动缓存之后):

$ time wc -l < /tmp/junk
real 0.280s user 0.156s sys 0.124s (total cpu 0.280s)$ time cat /tmp/junk | wc -l
real 0.407s user 0.157s sys 0.618s (total cpu 0.775s)$ time sh -c 'cat /tmp/junk | wc -l'real 0.411s user 0.118s sys 0.660s (total cpu 0.778s)

请注意,这两个管道结果声称占用的CPU时间(用户+sys)比实时时间要多。这是因为我使用的是shell(Bash)内置的‘time’命令,它知道管道;我在一台多核机器上,管道中的单独进程可以使用不同的内核,积累CPU时间比实时快。使用/usr/bin/time,我看到的CPU时间比实时的要小-表明它只能对命令行上传递给它的单个管道元素进行计时。另外,shell的输出给出毫秒,而/usr/bin/time只给出百分之一秒。

因此,在“wc-l”的效率级别上,“cat”产生了巨大的差异:409/283=1.453或45.3%的实时,775/280=2.768,或高达177%的使用CPU!在我的随机测试箱里。

我应该补充一点,在这些测试风格之间至少还有一个显著的区别,我不能说它是有利的还是错误的;你必须自己决定:

当您运行`Cat bix_file\/usr/bin/time my_Programm‘时,您的程序正在接收来自管道的输入,其速度与“cat”所发送的速度正好相同,并且以不大于“cat”编写的块的方式进行。

当您运行`/usr/bin/timemy_Program<Big_file‘时,您的程序将收到一个打开的文件描述符到实际文件。你的节目-在许多情况下,编写它的语言的I/O库-当显示引用常规文件的文件描述符时,可能会采取不同的操作。它可以使用mmap(2)将输入文件映射到其地址空间,而不是使用显式读取(2)系统调用。这些差异对基准结果的影响可能比运行“cat”二进制文件的成本小得多。

当然,如果相同的程序在两种情况下的执行情况明显不同,这将是一个有趣的基准结果。它表明,实际上,程序或其I/O库做一些有趣的事情,比如使用mmap()。因此,在实践中,最好是以两种方式来运行基准;也许可以用一些小因素来“原谅”运行`cat‘本身的成本,从而将`cat’结果打折扣。


查看完整回答
反对 回复 2019-06-21
  • 3 回答
  • 0 关注
  • 784 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信