为什么在包含iostream时这段代码运行得更快?

7
更快的代码:
#include <stdio.h>
#include <iostream>

long fib(int num)
{
   if (num <= 1)
       return 1;
   else
       return fib(num-1) + fib(num-2);
}
int main()
{
    long res = fib(45);
    printf("%li\n", res);
    return 0;
}

较慢的代码:

#include <stdio.h>

long fib(int num)
{
   if (num <= 1)
       return 1;
   else
       return fib(num-1) + fib(num-2);
}
int main()
{
    long res = fib(45);
    printf("%li\n", res);
    return 0;
}

两者唯一的区别在于第二行 #include <iostream>
两者都是使用clang++ 8.0.0-3编译的,并且使用了-O2标志。
clang++-8 -O2 fib.cpp && time ./a.out    # 3.59s
clang++-8 -O2 fib_io.cpp && time ./a.out # 3.15s

编辑:
看起来在重新启动后行为有所改变,iostream版本这次变慢了,这样更有意义。
我倾向于认为这只是个巧合,因为我无法再次重现。


评论不适合进行长时间的讨论;此对话已被移至聊天室 - Bhargav Rao
1
@BhargavRao 这让我被排除在进一步的参与之外,但是好吧 :-P ... - πάντα ῥεῖ
刚在Xeon E5-2650 v3上尝试了一下:没有使用 iostream 的代码速度更快。 - Evg
2
不稳定的测试和无法重现的行为。投票关闭。 - Attersson
2
@Attersson 我必须同意,因为我似乎再也无法复现它了。 - Kitegi
显示剩余2条评论
1个回答

2
当你包含 #include <iostream> 时,至少会有一个副作用: 必须构造和销毁 std::ios_base::Init 的实例 (参见 C++ 草案 [ios.init]p1):

Init 描述了一个对象,其构造确保构造了在 <iostream> 中声明的八个对象 ([iostream.objects]),这些对象将文件流缓冲区与由在 <cstdio> 中声明的函数提供的标准 C 流相关联。

来自 cppreference 的解释:

该类用于确保默认的 C++ 流 (std::cin, std::cout 等) 被正确初始化和销毁。该类跟踪创建了多少个它的实例,并在第一个实例被构造时初始化 C++ 流,并在最后一个实例被销毁时刷新输出流。

头文件 <iostream> 的行为就像它定义了一个具有静态存储期的 std::ios_base::Init 实例: 这使得在具有有序初始化的静态对象的构造函数和析构函数中访问标准 I/O 流是安全的 (只要在这些对象被定义之前在翻译单元中包含了 #include <iostream>)

这不一定意味着性能会有所不同(无论是更好还是更差)。然而,从 C++ 标准的角度来看,这意味着你的两个程序是不相等的。

如果不查看给定标准库的实际实现(或对其进行分析),我们无法知道详细原因(请随意这样做并添加答案!)。

检查在 Linux 系统上使用 clang 生成的代码(似乎是您的情况),即 libstdc++:

_GLOBAL__sub_I_a.cpp:             # @_GLOBAL__sub_I_a.cpp
        push    rax
        mov     edi, offset std::__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edi, offset std::ios_base::Init::~Init() [complete object destructor]
        mov     esi, offset std::__ioinit
        mov     edx, offset __dso_handle
        pop     rax
        jmp                 # TAILCALL

因此,无论是std::ios_base::Init::Init()还是__cxa_atexit都具有某些副作用,使整个程序对您来说更快。"最初的回答"

1
“一些副作用会使整个程序更快”这句话说得太模糊了…… - Attersson
1
我们知道有一些额外的函数,问题是为什么使用额外的函数会更快。 - Oblivion
1
抱歉如果我显得有些对抗。你提供了一个很好的描述。不幸的是,这并不是一个答案。我认为它仍然是有用的。 - Attersson
@oblivion 当然有一些区别,至少对于OP来说是这样——毕竟这就是问题所在。迄今为止,没有人提供关于什么的答案。此外,这可能取决于特定的标准库(或编译器、标志……)。本答案涉及到了为什么iostream不是一个无副作用头文件,根据C++标准,这是C++方面的一个答案。 - Acorn
1
@Xosrov,请查看Attersson在聊天中的解释。这是相当合理的:https://chat.stackoverflow.com/transcript/message/46571601#46571601 - Evg
显示剩余3条评论

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