在C++中重新声明变量是否需要花费任何成本?

Evo*_*lor 8 c++ performance declaration

为了便于阅读,我认为下面的第一个代码块更好.但第二个代码块是否更快?

第一块:

for (int i = 0; i < 5000; i++){
    int number = rand() % 10000 + 1;
    string fizzBuzz = GetStringFromFizzBuzzLogic(number);
}
Run Code Online (Sandbox Code Playgroud)

第二块:

int number;
string fizzBuzz;
for (int i = 0; i < 5000; i++){
    number = rand() % 10000 + 1;
    fizzBuzz = GetStringFromFizzBuzzLogic(number);
}
Run Code Online (Sandbox Code Playgroud)

在C++中重新声明变量是否需要花费任何成本?

bru*_*875 8

任何现代编译器都会注意到这一点并进行优化工作.如有疑问,请始终考虑可读性.尽可能在最内部范围内声明变量.

  • 任何现代编译器都能够优化第二种到第一种形式吗?你确定吗?请记住,必须在as-if-rule下保留可观察行为. (2认同)

Mat*_*son 6

我对这个特定的代码进行了基准测试,甚至没有进行优化,这两个版本的运行时几乎都相同。最低的优化级别一旦打开,结果就会非常接近相同(时间测量中有+/-一点噪声)。

编辑:下面对生成的汇编代码的分析表明,很难猜测哪种形式会更快,因为大多数人可能会给出的答案是func2,但是事实证明,至少在使用clang ++和-氧气 有充分的证据表明,“编写代码,基准,更改代码,基准”是处理性能的正确方法,而不是基于阅读代码来猜测。记住有人告诉我的内容,优化有点像将洋葱分层放置-一旦优化了一部分,您最终就会看到非常相似的东西,只是稍微小一点……;)

但是,我的最初分析func1速度明显变慢-出于某种奇怪的原因,这是因为编译器没有优化rand() % 10000 + 1in func1而是在func2关闭优化时进行了in的优化。这意味着func1。但是,一旦启用优化,两个功能都会获得“快速”模数。

使用linux性能工具perf显示,使用clang ++和-O2,我们可以得到func1的以下内容

  15.76%  a.out    libc-2.20.so         free
  12.31%  a.out    libstdc++.so.6.0.20  std::string::_S_construct<char cons
  12.29%  a.out    libc-2.20.so         _int_malloc
  10.05%  a.out    a.out                func1
   7.26%  a.out    libc-2.20.so         __random
   6.36%  a.out    libc-2.20.so         malloc
   5.46%  a.out    libc-2.20.so         __random_r
   5.01%  a.out    libstdc++.so.6.0.20  std::basic_string<char, std::char_t
   4.83%  a.out    libstdc++.so.6.0.20  std::string::_Rep::_S_create
   4.01%  a.out    libc-2.20.so         strlen
Run Code Online (Sandbox Code Playgroud)

对于func2:

  17.88%  a.out    libc-2.20.so         free
  10.73%  a.out    libc-2.20.so         _int_malloc                    
   9.77%  a.out    libc-2.20.so         malloc
   9.03%  a.out    a.out                func2                        
   7.63%  a.out    libstdc++.so.6.0.20  std::string::_S_construct<char con
   6.96%  a.out    libstdc++.so.6.0.20  std::string::_Rep::_S_create
   4.48%  a.out    libc-2.20.so         __random  
   4.39%  a.out    libc-2.20.so         __random_r
   4.10%  a.out    libc-2.20.so         strlen 
Run Code Online (Sandbox Code Playgroud)

有一些细微的差异,但我将其称为与基准测试的相对较短的运行时间有关,而不是与编译器生成的实际代码的差异有关。

这是下面的代码:

#include <iostream>
#include <string>
#include <cstdlib>

#define N 500000

extern std::string GetStringFromFizzBuzzLogic(int number);

void func1()
{
    for (int i = 0; i < N; i++){
        int number = rand() % 10000 + 1;
        std::string fizzBuzz = GetStringFromFizzBuzzLogic(number);
    }
}

void func2()
{
    int number;
    std::string fizzBuzz;
    for (int i = 0; i < N; i++){
        number = rand() % 10000 + 1;
        fizzBuzz = GetStringFromFizzBuzzLogic(number);
    }
}

static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}

int main(int argc, char **argv)
{

    void (*f)();

    if (argc == 1)
    f = func1;
    else
    f = func2;

    for(int i = 0; i < 5; i++)
    {
        unsigned long long t1 = rdtsc();

        f();
        t1 = rdtsc() - t1;

        std::cout << "time=" << t1 << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

并在一个单独的文件中:

#include <string>

std::string GetStringFromFizzBuzzLogic(int number)
{
    return "SomeString";
}
Run Code Online (Sandbox Code Playgroud)

使用func1运行:

./a.out
time=876016390
time=824149942
time=826812600
time=825266315
time=826151399
Run Code Online (Sandbox Code Playgroud)

使用func2运行:

./a.out
time=905721532
time=895393507
time=886537634
time=879836476
time=883887384
Run Code Online (Sandbox Code Playgroud)

在N上又添加了0,因此运行时间延长了10倍,这似乎比SLOWER慢了一点,但实际上是几个百分点,而且很可能在噪音范围内-整个基准时间大约在1.30-1.39之间秒。

编辑:查看实际循环的汇编代码[这只是循环的一部分,但其余部分在代码实际执行的方面是相同的]

Func1:

.LBB0_1:                                # %for.body
    callq   rand
    movslq  %eax, %rcx
    imulq   $1759218605, %rcx, %rcx # imm = 0x68DB8BAD
    movq    %rcx, %rdx
    shrq    $63, %rdx
    sarq    $44, %rcx
    addl    %edx, %ecx
    imull   $10000, %ecx, %ecx      # imm = 0x2710
    negl    %ecx
    leal    1(%rax,%rcx), %esi
    movq    %r15, %rdi
    callq   _Z26GetStringFromFizzBuzzLogici
    movq    (%rsp), %rax
    leaq    -24(%rax), %rdi
    cmpq    %rbx, %rdi
    jne .LBB0_2
.LBB0_7:                                # %_ZNSsD2Ev.exit
    decl    %ebp
    jne .LBB0_1
Run Code Online (Sandbox Code Playgroud)

Func2:

.LBB1_1:
    callq   rand
    movslq  %eax, %rcx
    imulq   $1759218605, %rcx, %rcx # imm = 0x68DB8BAD
    movq    %rcx, %rdx
    shrq    $63, %rdx
    sarq    $44, %rcx
    addl    %edx, %ecx
    imull   $10000, %ecx, %ecx      # imm = 0x2710
    negl    %ecx
    leal    1(%rax,%rcx), %esi
    movq    %rbx, %rdi
    callq   _Z26GetStringFromFizzBuzzLogici
    movq    %r14, %rdi
    movq    %rbx, %rsi
    callq   _ZNSs4swapERSs
    movq    (%rsp), %rax
    leaq    -24(%rax), %rdi
    cmpq    %r12, %rdi
    jne .LBB1_4
.LBB1_9:                                # %_ZNSsD2Ev.exit19
    incl    %ebp
    cmpl    $5000000, %ebp          # imm = 0x4C4B40
Run Code Online (Sandbox Code Playgroud)

因此,可以看出,该func2版本包含一个额外的函数调用:

    callq   _ZNSs4swapERSs
Run Code Online (Sandbox Code Playgroud)

它转换为std::basic_string<char, std::char_traits<char>, std::allocator<char> >::swap(std::basic_string<char, std::char_traits<char>, std::allocator<char> >&)std::string::swap(std::string&)-这大概是调用的结果std::string::operator=(std::string &s)。这可以解释为什么func2比慢一点func1

我敢肯定,有可能发现在一个循环中构造/销毁一个对象要花费大量时间的情况,但是总的来说,这几乎没有什么区别,而拥有更清晰的代码实际上会对读者有所帮助。它还通常会帮助编译器进行“生命周期分析”,因为“走动”以查找以后是否使用该变量的代码较少(在这种情况下,代码总是很短,但显然并非总是如此)在现实生活中的例子)