C++ 比 Bash 脚本写入文本文件快得多。

17

我想测试在bash脚本和C++程序中写入文件的性能。

这是bash脚本:

#!/bin/bash

while true; do
        echo "something" >> bash.txt
done

每秒向文本文件添加了大约2-3 KB。

以下是C++代码:

#include <iostream>
#include <fstream>

using namespace std;

int main() {
    ofstream myfile;
    myfile.open("cpp.txt");

    while (true) {
        myfile << "Writing this to a file Writing this to a file \n";
    }

    myfile.close();
}

这个操作在不到10秒的时间内就创建了一个约6GB的文本文件。

是什么让这个C++代码如此快,或者这个bash脚本如此慢?


16
我猜这里的主要区别在于批处理每次迭代都会打开和关闭文件,而 C++ 不会。尝试将 open() 和 close() 函数移至循环内部以便进行公平的性能比较(你需要传递 ios::app 参数给 open 函数)。 - IlBeldus
4
或者,在 shell 脚本的循环中进行重定向:while true; do ...; done >> bash.txt。意思是将循环中的输出内容追加到名为 bash.txt 的文件中。 - chepner
12
通过使用 strace 确认我的 bash 每次都会打开并关闭 bash.txt 文件。 - aschepler
3
看看这个愚蠢的小程序如何比较:`#include <fstream>int main() {while (true) { std::ofstream myfile("cpp.txt", std::ios::app); myfile << "Writing this to a file Writing this to a file \n"; }}`(注意:本翻译为直译,可能不符合中文习惯表达方式) - user4581301
1
此外,“每秒钟这会增加约2-3 KB到文本文件中。”——真的吗?使用什么硬件和操作系统呢?在我的电脑上(i5,Debian 8),我看到每秒钟超过1MB(在zsh中大约是两倍)。这是相当大的速度差异。 - marcelm
显示剩余11条评论
3个回答

39

有几个原因导致这种情况。

首先,解释执行环境(如bash、perl以及非JIT的lua和python等)通常比甚至是糟糕编写的编译程序(C、C++等)慢得多。

其次,注意您的bash代码是多么的碎片化——它只是将一行写入文件,然后再写一行,以此类推。另一方面,您的C++程序执行缓冲写操作,即使您没有直接努力去做。如果您用 C++ 程序替换bash程序,您可能会看到它运行得更慢。

myfile << "Writing this to a file Writing this to a file \n";

myfile << "Writing this to a file Writing this to a file" << endl;

要了解流在C++中的实现方式以及为什么\nendl不同,请参阅有关C ++的任何参考文档。

第三点,正如注释所证明的那样,您的bash脚本对于每一行都执行了目标文件的打开/关闭操作。这本身就意味着显著的性能开销 - 想象一下myfile.openmyfile.close移动到循环体内部!


3
把性能冲掉是一个不错的开始。下一步是在每个循环中打开文件并以追加方式关闭它。这样应该会更接近。 - user4581301
@user4581301 是的,我考虑过了(请参见编辑),但不太确定——我不是bash方面的专家 :) - iehrlich
2
据我所知,每次必须将bash行翻译/“构建”为本地代码。这对于perl不是真实的情况,因为它只被编译一次,或者对于python,因为它被编译成字节码。Bash直到运行命令前才会构建它,而perl在开始时就会构建所有内容等。 - code_dredd
1
“...解释执行环境(例如...Python...” - 然而,CPython是默认的Python实现,它将Python源代码编译为字节码,在虚拟机中运行(有些人称之为解释器,这使事情更加混乱)。我对Perl不是很熟悉,但如果它采用类似的结构,我也不会感到惊讶。我认为纯解释语言实现现在相当罕见。虽然我很确定Unix shell仍然是这样的。 - marcelm
std::endl\n之间的区别 - phuclv
显示剩余2条评论

6

正如其他人已经指出的那样,这是因为您目前正在使用每行写入脚本时打开和关闭文件(而shell脚本在解释时C ++则被编译)。 您可以将写入批处理并进行一次写入,例如

MSG="something"
logfile="test.txt"
(
for i in {1..10000}; do
        echo $MSG
done
) >> $logfile

这将会写入消息10k次,但仅打开日志一次。


1
echo 是 Bash 内置命令。 - Basile Starynkevitch
@BasileStarynkevitch 好的,晚上好。那个话题有些离题了,所以我把它删掉了。 - Elliott Frisch

-3

5
有时候,翻译后的语言会包含一些小技巧,它们被优化得非常棒,以至于超出了预期。 - user4581301
1
@user4581301 嗯,严格来说它们在这一点上并不是解释执行的,而是JIT/AOT编译的 ;) - iehrlich
1
不...Bash是解释性的,虽然它们可以很快,但你仍然必须“解释”,这总是会有点慢。你可以编译它,但这不是我们讨论的内容。 - Reece Ward
1
@iehrlich 即使没有JIT编译,有时你也会遇到“天啊!”。旧版Matlab就是一个很好的例子。脚本运行缓慢,但支持脚本的代码却非常快速。 - user4581301
6
在这种情况下,解释型、即时编译和编译型并不是特别相关,因为 I/O 是瓶颈。CPU 使用率将在整个程序执行期间保持在低于 1% 的水平,因此在那 1% 时间内 C++ 版本更快并不重要。iehrlich的答案是正确的;问题在于 Bash 脚本每次打印一行时都会重新打开文件,而 C++ 版本会保持文件打开状态直到完成为止。 - Ray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接