相同的C和C++程序之间存在很大的速度差异

15

我对 c 和 c++ 编程非常新手,所以我从基础开始学习。我编写了两个完全相同的斐波那契循环程序,用于测试它们的相对速度。我认为对于这么简单的东西它们应该差不多,但是 c++ 版本要慢 60 倍。它们只是循环并打印前14个斐波那契数列数值,重复10,000次。这是 c 版本:

#include <stdio.h>

int main (){
    int c = 0;
    int x, y, z;

    while(c < 10000)
    {
        x = 0;
        y = 1;
        while(x < 255)
        {
            printf("%d\n", x);
            z = x + y;
            x = y;
            y = z;
        }
        c++;
    }
    return 0;
}

这是 C++ 版本:

#include <iostream>
using namespace std;

int main()
{
    int c = 0, x = 0, y = 0, z = 0;
    while(c < 10000)
    {
        x = 0;
        y = 1;
        while(x < 255)
        {
            cout << x << endl;
            z = x + y;
            x = y;
            y = z;
        }
        c++;
    }
    return 0;
}

我在Notepad ++中编写,并使用Codeblocks附带的mingw通过g++进行编译:

g++ -o fibc.exe fib.c -s
g++ -o fibcpp.exe fib.cpp -s

可执行文件大小差异很大:c语言是8.5KB而c++则为784KB!我使用powershell对它们进行了时间测试:

Measure-Command {start-process "C:\Path\fibcpp.exe" -RedirectStandardOutput "C:\Path\cpp.txt" -Wait}

产生的文件是相同的,但C版本只需要1秒,而C++版本则需要60秒!(事实上,将C程序循环1百万次仍然只需要13秒)。我也用Visual Studio 17编写了C++并使用x86发布配置进行了编译。现在程序大小为9.5KB,但运行时间与g ++版本相同:62秒。为什么对于这样一个简单的程序会出现这种情况?


6
尝试在 C++ 代码中使用与 C 代码相同的标题。删除 using namespace std; 并使用 printf 替代 cout,并再次检查它们之间的差异。当进行这样的实验时,应该尽可能地让两个代码接近。 - andresantacruz
11
首先建议尝试使用'\n'替代std::endl,这样就不需要每次都刷新。你尝试过使用优化编译器进行编译吗,例如-O2 - Bob__
11
你没有比较等价的代码。C++ 流(std::cout 等)默认情况下与它们的 C 语言版本同步 - 这意味着默认情况下会有性能损失。在执行输出之前尝试使用 std::ios::sync_with_stdio(false),然后 C++ 版本将更接近(虽然不完全等同于)C 版本。此外,endl 流操作符会刷新流缓冲区,而在向 printf() 提供 '\n' 时不会这样做。另外,在未经优化的代码上进行基准测试通常是无意义的。 - Peter
1
你能用printf替换cout吗? - Mike
3
嗯,你们在两分钟内解决了我的问题-谢谢。在C++代码中切换到stdio和printf就是一切 - 新的exe与C版本的大小和性能相同。如你所见,我非常新手。 - MrPuzzler
显示剩余13条评论
3个回答

6
您正在对比printfcout的性能,因为它们是您程序中的主要瓶颈。 printf是一个非常慢的函数,但可能仍然比osteam快,后者必须维护自己私有的模板元编程地狱,以保持符合C++标准所需的灵活性。 cout肯定比printf更灵活和复杂。更多的功能意味着更慢的代码。
如果您真正想比较这两种语言,请删除打印函数并将它们替换为具有外部链接的虚拟函数“foo”。
void foo (int x);
...

while(x < 255)
{
  foo(x);

当我使用gcc -O3对x86的代码进行基准测试时,C语言版本C++语言版本的代码几乎完全相同。

唯一显著的区别是C++在main()函数结尾处添加了"CRT"。它调用了atexit和各种其他清理操作,这些操作C语言也会执行,但可能在应用程序代码之外。然而,在具有静态存储期的对象中调用构造函数和析构函数会导致C++始终存在开销。而C语言则不需要执行这些操作。


如果你不包含iostream,那么这两者实际上是相同的(除了foo的名称混淆)。关键是仅仅_包含_ iostream 就会对程序产生一些(小)开销。 - Miles Budnek
@MilesBudnek 我更倾向于认为包含任何具有静态存储期对象的内容都会产生开销。 - Lundin
3
模板元编程不会影响执行速度。像缓冲策略、刷新和同步等内容会对速度产生影响。 - aschepler
@aschepler 各种开销代码都会影响执行速度。可以在编译时解决的问题不会影响速度,但元编程几乎总是意味着开销代码。因此,ostream非常慢。 - Lundin

2
C++的cout慢的原因在这里中有解释。

默认情况下,iostream对象和cstdio流是同步的(就好像使用true作为参数调用此函数一样)。

因此,默认情况下cout与stdio同步。
请尝试执行以下操作以加快速度。
ios_base::sync_with_stdio(false)

但要小心,这段代码会打印出你意料之外的东西。
#include <iostream>
#include <cstdio>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cout << "a\n";
    std::printf("b\n");
    std::cout << "c\n";
}

0

编译中

#include <iostream>
using namespace std;

int main()
{
    ios_base::sync_with_stdio(false);
    int c = 0, x = 0, y = 0, z = 0;
    while(c < 10000)
    {
        x = 0;
        y = 1;
        while(x < 255)
        {
            cout << x << '\n';
            z = x + y;
            x = y;
            y = z;
        }
        c++;
    }
    return 0;
}

使用基本的现代优化(-Os)得到

$ ls -l bin/c?
-rwxr-xr-x 1 jthill jthill 16592 Feb 29 12:47 bin/c1
-rwxr-xr-x 1 jthill jthill 17176 Feb 29 12:53 bin/c2
-rwxr-xr-x 1 jthill jthill 17088 Feb 29 12:49 bin/c3
$ 

(你的C版本是c1,C++版本是c2,上述C++版本是c3)。

每个程序运行100次后计算平均时间,

$ for x in {1..100}; do time bin/c1; done 2>&1 >$scratch | awk '/user/{++n; sub(/0m/,"",$2); tot+=$2}END{print tot/n}'
0.00862
$ for x in {1..100}; do time bin/c2; done 2>&1 >$scratch | awk '/user/{++n; sub(/0m/,"",$2); tot+=$2}END{print tot/n}'
0.0428
$ for x in {1..100}; do time bin/c3; done 2>&1 >$scratch | awk '/user/{++n; sub(/0m/,"",$2); tot+=$2}END{print tot/n}'
0.00743

如果使用得当,C ++ 的 iostream 比 C 的 stdio 更快。


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