C++ 在C++;要花点钱吗?
为了可读性,我认为下面的第一个代码块更好。但是第二个代码块更快吗 第一个街区:C++ 在C++;要花点钱吗?,c++,performance,declaration,C++,Performance,Declaration,为了可读性,我认为下面的第一个代码块更好。但是第二个代码块更快吗 第一个街区: for (int i = 0; i < 5000; i++){ int number = rand() % 10000 + 1; string fizzBuzz = GetStringFromFizzBuzzLogic(number); } for(int i=0;i 强>如果程序将被认为太慢而强>如果代码的哪些部分会导致减速和 >如果< /强>指向这个循环的测量,那么他们会考虑改变它。 然而
for (int i = 0; i < 5000; i++){
int number = rand() % 10000 + 1;
string fizzBuzz = GetStringFromFizzBuzzLogic(number);
}
for(int i=0;i<5000;i++){
整数=兰德()%10000+1;
字符串fizzBuzz=GetStringFromFizzBuzzLogic(数字);
}
第二个街区:
int number;
string fizzBuzz;
for (int i = 0; i < 5000; i++){
number = rand() % 10000 + 1;
fizzBuzz = GetStringFromFizzBuzzLogic(number);
}
整数;
弦乐嘶嘶作响;
对于(int i=0;i<5000;i++){
数字=兰德()%10000+1;
fizzBuzz=GetStringFromFizzBuzzLogic(编号);
}
<>在C++中重新声明变量是否有代价?< p>任何现代编译器都会注意到这一点并进行优化工作。
如果有疑问,一定要确保可读性。尽可能在最内部的范围内声明变量。第一个代码块应该被认为更快,因为一次调用
std::string
默认构造函数没有任何开销
实际上,在第二个代码块中没有变量的重新声明。这些只是简单的赋值操作
重新声明实际上意味着你有这样的东西
int number;
string fizzBuzz;
for (int i = 0; i < 5000; i++){
int number = rand() % 10000 + 1;
// ^^^
string fizzBuzz = GetStringFromFizzBuzzLogic(number);
// ^^^^^^
}
整数;
弦乐嘶嘶作响;
对于(int i=0;i<5000;i++){
整数=兰德()%10000+1;
// ^^^
字符串fizzBuzz=GetStringFromFizzBuzzLogic(数字);
// ^^^^^^
}
在这种情况下,编译器将优化开销,因为根本不使用外部作用域变量。所有声明(值)变量都是通过该函数/方法中所有局部变量的组合大小来增加堆栈
调用构造函数/析构函数的代价可能超过对象类型(字符串)的最佳次数
在这种情况下没有区别。如果使用合适的编译器,优化器将为您提供最好的解决方案
您可能希望以最佳方式阅读代码,这样您的同事就不会认为您编写了糟糕的代码 > C++中没有再声明的东西。在第二个代码段中,
number
和fizzBuzz
仅声明和初始化一次。后面的=
是作业
与所有优化问题一样,您只能猜测或最好测量。当然,这完全取决于你的编译器和你调用它的设置。当然,在速度优化和空间优化之间可以进行权衡
我知道没有一个严肃的C++程序员不喜欢第一种形式,因为它更容易阅读,而且更简洁。
<> > <>强>如果程序将被认为太慢而<>强>如果代码的哪些部分会导致减速和<强> >如果< /强>指向这个循环的测量,那么他们会考虑改变它。
然而,正如其他人所说,这是一种不现实的情况。在优化方面,现代编译器极不可能以不同的方式处理这两个代码段,也不可能遇到任何可测量的速度差异
(编辑:很抱歉输入错误,我把“第一”和“第二”混淆了)我对这段代码进行了基准测试,即使没有优化,这两种变体的运行时间也几乎相同。一旦最低级别的优化被打开,结果就非常接近于相同的结果(时间测量中有一点噪音) 编辑:下面对生成的汇编程序代码的分析表明,很难猜测哪种形式更快,因为大多数人可能会给出的答案是
func2
,但事实证明该函数稍微慢一点,至少在使用clang++和-O2编译时是这样。这很好地证明了“写代码、基准测试、更改代码、基准测试”是处理性能的正确方法,而不是基于阅读代码的猜测。还记得有人告诉我的,优化有点像把洋葱分层——一旦你优化了一部分,你最终会看到非常相似的东西,只是稍微小一点而已……)
然而,我最初的分析使func1
的速度明显变慢,这是因为编译器出于某种原因,没有在func1
中优化rand()%10000+1
,而是在func2
中优化。这意味着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
使用func2运行:
./a.out
time=905721532
time=895393507
time=886537634
time=879836476
time=883887384
这是在N中再加上一个0——因此运行时间延长了10倍——看起来它一直都有点慢,但它只有几个百分点,而且可能在噪音范围内,真的——在时间上,整个基准测试大约需要1.30-1.39秒
编辑:查看实际循环的汇编代码[这只是循环的一部分,但其余部分在代码的实际作用方面是相同的]
职能1:
.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
职能2:
.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
因此,可以看出,func2
版本包含一个额外的函数调用:
callq _ZNSs4swapERSs
转换为std::basic_string::swap(std::basic_string&)
或std::string::swap(std::string&)
——这可能是调用std::string::operator=(std::string&s)
的结果。这可以解释为什么func2
比func1
稍慢
我确信在循环中构建/销毁一个对象需要花费大量时间的情况是可能的,但一般来说,这不会有什么区别,或者根本不会有什么区别,而更清晰的代码实际上会帮助读者。它还经常帮助编译器进行“生命周期分析”,因为要“遍历”变量以确定变量是否在以后使用,所需的代码更少(在本例中,代码很短,但在实际示例中显然并不总是如此)“确实如此
./a.out
time=905721532
time=895393507
time=886537634
time=879836476
time=883887384
.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
.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
callq _ZNSs4swapERSs