当包含iostream时,为什么此代码更快?

时间:2019-06-22 09:35:21

标签: c++

快速代码:

#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>

二者均使用带有-O2标志的clang ++ 8.0.0-3编译。

clang++-8 -O2 fib.cpp && time ./a.out    # 3.59s
clang++-8 -O2 fib_io.cpp && time ./a.out # 3.15s

编辑:
重新启动后,行为似乎发生了变化,这次iostream版本变慢了,这更有意义。
我倾向于说这只是a幸,因为我再也无法复制了。

1 个答案:

答案 0 :(得分:2)

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

  

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

cppreference中对此的解释:

  

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

     

标头<iostream>的行为就像它(直接或间接)定义了具有静态存储持续时间的std::ios_base::Init的实例一样:这样可以安全地访问构造函数和析构函数中的标准I / O流有序初始化的静态对象(只要在定义这些对象之前在转换单元中包含#include <iostream>

这不一定意味着性能应该有所不同(更好或更差)。但是,从C ++ Standard的角度来看,这意味着您的两个程序相等。

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

在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都有一些副作用,使您的整体程序更快。