C++ 可以使用goto创建编译器可以';t在C+中生成+;?
使用gotos而不是oops会导致一系列跳转指令比使用循环时编译器生成的指令更高效吗C++ 可以使用goto创建编译器可以';t在C+中生成+;?,c++,C++,使用gotos而不是oops会导致一系列跳转指令比使用循环时编译器生成的指令更高效吗 例如:如果我在switch语句中嵌套了一个while循环,它将嵌套在另一个循环中,而这个循环将嵌套在另一个switch语句中,使用goto是否真的比编译器在使用循环和非goto时生成的跳转指令更聪明?使用goto可能会获得一点速度优势。然而,情况可能恰恰相反。编译器在使用SIMD指令检测和展开循环或优化循环方面已经变得非常出色。您很可能会删除编译器的所有这些优化选项,因为它们不是为优化goto语句而构建的。 您
例如:如果我在switch语句中嵌套了一个while循环,它将嵌套在另一个循环中,而这个循环将嵌套在另一个switch语句中,使用goto是否真的比编译器在使用循环和非goto时生成的跳转指令更聪明?使用goto可能会获得一点速度优势。然而,情况可能恰恰相反。编译器在使用SIMD指令检测和展开循环或优化循环方面已经变得非常出色。您很可能会删除编译器的所有这些优化选项,因为它们不是为优化goto语句而构建的。 您还可以编写函数来防止gotos。这样可以使编译器内联函数并消除跳转 <>如果你考虑使用GOTO进行优化,我会说,这是一个非常糟糕的想法。用干净的代码表达您的算法,并在以后进行优化。如果您需要更高的性能,请考虑您的数据和对数据的访问。这就是你可以获得或失去性能的关键所在 因为您需要代码来证明这一点,所以我构建了以下示例。我在Intel i7-3537U上使用了gcc 6.3.1和-O3。如果您试图重现该示例,您的结果可能会因编译器或硬件的不同而有所不同
#include <iostream>
#include <array>
#include "ToolBox/Instrumentation/Profiler.hpp"
constexpr size_t kilo = 1024;
constexpr size_t mega = kilo * 1024;
constexpr size_t size = 512*mega;
using container = std::array<char, size>;
enum class Measurements {
jump,
loop
};
// Simple vector addition using for loop
void sum(container& result, const container& data1, const container& data2) {
profile(Measurements::loop);
for(unsigned int i = 0; i < size; ++i) {
result[i] = data1[i] + data2[i];
}
}
// Simple vector addition using jumps
void sum_jump(container& result, const container& data1, const container& data2) {
profile(Measurements::jump);
unsigned int i = 0;
label:
result[i] = data1[i] + data2[i];
i++;
if(i == size) goto label;
}
int main() {
// This segment is just for benchmarking purposes
// Just ignore this
ToolBox::Instrumentation::Profiler<Measurements, std::chrono::nanoseconds, 2> profiler(
std::cout,
{
{Measurements::jump, "jump"},
{Measurements::loop, "loop"}
}
);
// allocate memory to execute our sum functions on
container data1, data2, result;
// run the benchmark 100 times to account for caching of the data
for(unsigned i = 0; i < 100; i++) {
sum_jump(result, data1, data2);
sum(result, data1, data2);
}
}
好的,我们看到运行时中并没有时间差,因为我们在内存带宽上受到限制,而不是cpu指令上。但让我们看看生成的汇编程序指令:
Dump of assembler code for function sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&):
0x00000000004025c0 <+0>: push %r15
0x00000000004025c2 <+2>: push %r14
0x00000000004025c4 <+4>: push %r12
0x00000000004025c6 <+6>: push %rbx
0x00000000004025c7 <+7>: push %rax
0x00000000004025c8 <+8>: mov %rdx,%r15
0x00000000004025cb <+11>: mov %rsi,%r12
0x00000000004025ce <+14>: mov %rdi,%rbx
0x00000000004025d1 <+17>: callq 0x402110 <_ZNSt6chrono3_V212system_clock3nowEv@plt>
0x00000000004025d6 <+22>: mov %rax,%r14
0x00000000004025d9 <+25>: lea 0x20000000(%rbx),%rcx
0x00000000004025e0 <+32>: lea 0x20000000(%r12),%rax
0x00000000004025e8 <+40>: lea 0x20000000(%r15),%rsi
0x00000000004025ef <+47>: cmp %rax,%rbx
0x00000000004025f2 <+50>: sbb %al,%al
0x00000000004025f4 <+52>: cmp %rcx,%r12
0x00000000004025f7 <+55>: sbb %dl,%dl
0x00000000004025f9 <+57>: and %al,%dl
0x00000000004025fb <+59>: cmp %rsi,%rbx
0x00000000004025fe <+62>: sbb %al,%al
0x0000000000402600 <+64>: cmp %rcx,%r15
0x0000000000402603 <+67>: sbb %cl,%cl
0x0000000000402605 <+69>: test $0x1,%dl
0x0000000000402608 <+72>: jne 0x40268b <sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&)+203>
0x000000000040260e <+78>: and %cl,%al
0x0000000000402610 <+80>: and $0x1,%al
0x0000000000402612 <+82>: jne 0x40268b <sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&)+203>
0x0000000000402614 <+84>: xor %eax,%eax
0x0000000000402616 <+86>: nopw %cs:0x0(%rax,%rax,1)
0x0000000000402620 <+96>: movdqu (%r12,%rax,1),%xmm0
0x0000000000402626 <+102>: movdqu 0x10(%r12,%rax,1),%xmm1
0x000000000040262d <+109>: movdqu (%r15,%rax,1),%xmm2
0x0000000000402633 <+115>: movdqu 0x10(%r15,%rax,1),%xmm3
0x000000000040263a <+122>: paddb %xmm0,%xmm2
0x000000000040263e <+126>: paddb %xmm1,%xmm3
0x0000000000402642 <+130>: movdqu %xmm2,(%rbx,%rax,1)
0x0000000000402647 <+135>: movdqu %xmm3,0x10(%rbx,%rax,1)
0x000000000040264d <+141>: movdqu 0x20(%r12,%rax,1),%xmm0
0x0000000000402654 <+148>: movdqu 0x30(%r12,%rax,1),%xmm1
0x000000000040265b <+155>: movdqu 0x20(%r15,%rax,1),%xmm2
0x0000000000402662 <+162>: movdqu 0x30(%r15,%rax,1),%xmm3
0x0000000000402669 <+169>: paddb %xmm0,%xmm2
0x000000000040266d <+173>: paddb %xmm1,%xmm3
0x0000000000402671 <+177>: movdqu %xmm2,0x20(%rbx,%rax,1)
0x0000000000402677 <+183>: movdqu %xmm3,0x30(%rbx,%rax,1)
0x000000000040267d <+189>: add $0x40,%rax
0x0000000000402681 <+193>: cmp $0x20000000,%rax
0x0000000000402687 <+199>: jne 0x402620 <sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&)+96>
0x0000000000402689 <+201>: jmp 0x4026d5 <sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&)+277>
0x000000000040268b <+203>: xor %eax,%eax
0x000000000040268d <+205>: nopl (%rax)
0x0000000000402690 <+208>: movzbl (%r15,%rax,1),%ecx
0x0000000000402695 <+213>: add (%r12,%rax,1),%cl
0x0000000000402699 <+217>: mov %cl,(%rbx,%rax,1)
0x000000000040269c <+220>: movzbl 0x1(%r15,%rax,1),%ecx
0x00000000004026a2 <+226>: add 0x1(%r12,%rax,1),%cl
0x00000000004026a7 <+231>: mov %cl,0x1(%rbx,%rax,1)
0x00000000004026ab <+235>: movzbl 0x2(%r15,%rax,1),%ecx
0x00000000004026b1 <+241>: add 0x2(%r12,%rax,1),%cl
0x00000000004026b6 <+246>: mov %cl,0x2(%rbx,%rax,1)
0x00000000004026ba <+250>: movzbl 0x3(%r15,%rax,1),%ecx
0x00000000004026c0 <+256>: add 0x3(%r12,%rax,1),%cl
0x00000000004026c5 <+261>: mov %cl,0x3(%rbx,%rax,1)
0x00000000004026c9 <+265>: add $0x4,%rax
0x00000000004026cd <+269>: cmp $0x20000000,%rax
0x00000000004026d3 <+275>: jne 0x402690 <sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&)+208>
0x00000000004026d5 <+277>: callq 0x402110 <_ZNSt6chrono3_V212system_clock3nowEv@plt>
0x00000000004026da <+282>: sub %r14,%rax
0x00000000004026dd <+285>: add %rax,0x202b74(%rip) # 0x605258 <_ZN7ToolBox15Instrumentation6detail19ProfilerMeasurementI12MeasurementsLS3_1EE14totalTimeSpentE>
0x00000000004026e4 <+292>: incl 0x202b76(%rip) # 0x605260 <_ZN7ToolBox15Instrumentation6detail19ProfilerMeasurementI12MeasurementsLS3_1EE10executionsE>
0x00000000004026ea <+298>: add $0x8,%rsp
0x00000000004026ee <+302>: pop %rbx
0x00000000004026ef <+303>: pop %r12
0x00000000004026f1 <+305>: pop %r14
0x00000000004026f3 <+307>: pop %r15
0x00000000004026f5 <+309>: retq
End of assembler dump.
我也尝试了这种矩阵乘法方法,但是gcc足够聪明,可以用goto/标签语法检测矩阵乘法
结论:
即使我们没有看到速度损失,我们看到,gcc不能使用优化,这可以加速计算。在cpu更具挑战性的任务中,这可能会导致性能下降。使用goto可能会获得较小的速度优势。然而,情况可能恰恰相反。编译器在使用SIMD指令检测和展开循环或优化循环方面已经变得非常出色。您很可能会删除编译器的所有这些优化选项,因为它们不是为优化goto语句而构建的。 您还可以编写函数来防止gotos。这样可以使编译器内联函数并消除跳转 <>如果你考虑使用GOTO进行优化,我会说,这是一个非常糟糕的想法。用干净的代码表达您的算法,并在以后进行优化。如果您需要更高的性能,请考虑您的数据和对数据的访问。这就是你可以获得或失去性能的关键所在 因为您需要代码来证明这一点,所以我构建了以下示例。我在Intel i7-3537U上使用了gcc 6.3.1和-O3。如果您试图重现该示例,您的结果可能会因编译器或硬件的不同而有所不同
#include <iostream>
#include <array>
#include "ToolBox/Instrumentation/Profiler.hpp"
constexpr size_t kilo = 1024;
constexpr size_t mega = kilo * 1024;
constexpr size_t size = 512*mega;
using container = std::array<char, size>;
enum class Measurements {
jump,
loop
};
// Simple vector addition using for loop
void sum(container& result, const container& data1, const container& data2) {
profile(Measurements::loop);
for(unsigned int i = 0; i < size; ++i) {
result[i] = data1[i] + data2[i];
}
}
// Simple vector addition using jumps
void sum_jump(container& result, const container& data1, const container& data2) {
profile(Measurements::jump);
unsigned int i = 0;
label:
result[i] = data1[i] + data2[i];
i++;
if(i == size) goto label;
}
int main() {
// This segment is just for benchmarking purposes
// Just ignore this
ToolBox::Instrumentation::Profiler<Measurements, std::chrono::nanoseconds, 2> profiler(
std::cout,
{
{Measurements::jump, "jump"},
{Measurements::loop, "loop"}
}
);
// allocate memory to execute our sum functions on
container data1, data2, result;
// run the benchmark 100 times to account for caching of the data
for(unsigned i = 0; i < 100; i++) {
sum_jump(result, data1, data2);
sum(result, data1, data2);
}
}
好的,我们看到运行时中并没有时间差,因为我们在内存带宽上受到限制,而不是cpu指令上。但让我们看看生成的汇编程序指令:
Dump of assembler code for function sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&):
0x00000000004025c0 <+0>: push %r15
0x00000000004025c2 <+2>: push %r14
0x00000000004025c4 <+4>: push %r12
0x00000000004025c6 <+6>: push %rbx
0x00000000004025c7 <+7>: push %rax
0x00000000004025c8 <+8>: mov %rdx,%r15
0x00000000004025cb <+11>: mov %rsi,%r12
0x00000000004025ce <+14>: mov %rdi,%rbx
0x00000000004025d1 <+17>: callq 0x402110 <_ZNSt6chrono3_V212system_clock3nowEv@plt>
0x00000000004025d6 <+22>: mov %rax,%r14
0x00000000004025d9 <+25>: lea 0x20000000(%rbx),%rcx
0x00000000004025e0 <+32>: lea 0x20000000(%r12),%rax
0x00000000004025e8 <+40>: lea 0x20000000(%r15),%rsi
0x00000000004025ef <+47>: cmp %rax,%rbx
0x00000000004025f2 <+50>: sbb %al,%al
0x00000000004025f4 <+52>: cmp %rcx,%r12
0x00000000004025f7 <+55>: sbb %dl,%dl
0x00000000004025f9 <+57>: and %al,%dl
0x00000000004025fb <+59>: cmp %rsi,%rbx
0x00000000004025fe <+62>: sbb %al,%al
0x0000000000402600 <+64>: cmp %rcx,%r15
0x0000000000402603 <+67>: sbb %cl,%cl
0x0000000000402605 <+69>: test $0x1,%dl
0x0000000000402608 <+72>: jne 0x40268b <sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&)+203>
0x000000000040260e <+78>: and %cl,%al
0x0000000000402610 <+80>: and $0x1,%al
0x0000000000402612 <+82>: jne 0x40268b <sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&)+203>
0x0000000000402614 <+84>: xor %eax,%eax
0x0000000000402616 <+86>: nopw %cs:0x0(%rax,%rax,1)
0x0000000000402620 <+96>: movdqu (%r12,%rax,1),%xmm0
0x0000000000402626 <+102>: movdqu 0x10(%r12,%rax,1),%xmm1
0x000000000040262d <+109>: movdqu (%r15,%rax,1),%xmm2
0x0000000000402633 <+115>: movdqu 0x10(%r15,%rax,1),%xmm3
0x000000000040263a <+122>: paddb %xmm0,%xmm2
0x000000000040263e <+126>: paddb %xmm1,%xmm3
0x0000000000402642 <+130>: movdqu %xmm2,(%rbx,%rax,1)
0x0000000000402647 <+135>: movdqu %xmm3,0x10(%rbx,%rax,1)
0x000000000040264d <+141>: movdqu 0x20(%r12,%rax,1),%xmm0
0x0000000000402654 <+148>: movdqu 0x30(%r12,%rax,1),%xmm1
0x000000000040265b <+155>: movdqu 0x20(%r15,%rax,1),%xmm2
0x0000000000402662 <+162>: movdqu 0x30(%r15,%rax,1),%xmm3
0x0000000000402669 <+169>: paddb %xmm0,%xmm2
0x000000000040266d <+173>: paddb %xmm1,%xmm3
0x0000000000402671 <+177>: movdqu %xmm2,0x20(%rbx,%rax,1)
0x0000000000402677 <+183>: movdqu %xmm3,0x30(%rbx,%rax,1)
0x000000000040267d <+189>: add $0x40,%rax
0x0000000000402681 <+193>: cmp $0x20000000,%rax
0x0000000000402687 <+199>: jne 0x402620 <sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&)+96>
0x0000000000402689 <+201>: jmp 0x4026d5 <sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&)+277>
0x000000000040268b <+203>: xor %eax,%eax
0x000000000040268d <+205>: nopl (%rax)
0x0000000000402690 <+208>: movzbl (%r15,%rax,1),%ecx
0x0000000000402695 <+213>: add (%r12,%rax,1),%cl
0x0000000000402699 <+217>: mov %cl,(%rbx,%rax,1)
0x000000000040269c <+220>: movzbl 0x1(%r15,%rax,1),%ecx
0x00000000004026a2 <+226>: add 0x1(%r12,%rax,1),%cl
0x00000000004026a7 <+231>: mov %cl,0x1(%rbx,%rax,1)
0x00000000004026ab <+235>: movzbl 0x2(%r15,%rax,1),%ecx
0x00000000004026b1 <+241>: add 0x2(%r12,%rax,1),%cl
0x00000000004026b6 <+246>: mov %cl,0x2(%rbx,%rax,1)
0x00000000004026ba <+250>: movzbl 0x3(%r15,%rax,1),%ecx
0x00000000004026c0 <+256>: add 0x3(%r12,%rax,1),%cl
0x00000000004026c5 <+261>: mov %cl,0x3(%rbx,%rax,1)
0x00000000004026c9 <+265>: add $0x4,%rax
0x00000000004026cd <+269>: cmp $0x20000000,%rax
0x00000000004026d3 <+275>: jne 0x402690 <sum(std::array<char, 536870912ul>&, std::array<char, 536870912ul> const&, std::array<char, 536870912ul> const&)+208>
0x00000000004026d5 <+277>: callq 0x402110 <_ZNSt6chrono3_V212system_clock3nowEv@plt>
0x00000000004026da <+282>: sub %r14,%rax
0x00000000004026dd <+285>: add %rax,0x202b74(%rip) # 0x605258 <_ZN7ToolBox15Instrumentation6detail19ProfilerMeasurementI12MeasurementsLS3_1EE14totalTimeSpentE>
0x00000000004026e4 <+292>: incl 0x202b76(%rip) # 0x605260 <_ZN7ToolBox15Instrumentation6detail19ProfilerMeasurementI12MeasurementsLS3_1EE10executionsE>
0x00000000004026ea <+298>: add $0x8,%rsp
0x00000000004026ee <+302>: pop %rbx
0x00000000004026ef <+303>: pop %r12
0x00000000004026f1 <+305>: pop %r14
0x00000000004026f3 <+307>: pop %r15
0x00000000004026f5 <+309>: retq
End of assembler dump.
我也尝试了这种矩阵乘法方法,但是gcc足够聪明,可以用goto/标签语法检测矩阵乘法
结论:
即使我们没有看到速度损失,我们看到,gcc不能使用优化,这可以加速计算。在cpu更具挑战性的任务中,这可能会导致性能下降。会吗?当然,从理论上讲,如果您使用的是一个特别愚蠢的编译器,或者编译时禁用了优化 实际上,绝对不是。优化编译器在优化循环和开关语句方面没有什么困难。与按照正常规则玩游戏,使用优化器熟悉的循环结构并编程进行相应优化相比,您更可能将优化器与无条件跳转混淆 这只是回到了一个一般规则,即优化器最好地使用标准的惯用代码,因为这是他们经过培训要识别的。考虑到模式匹配的广泛使用,如果您偏离了正常模式,您就不太可能获得最佳的代码输出 例如:如果我在switch语句中嵌套了一个while循环,它将嵌套在另一个循环中,而这个循环将嵌套在另一个switch语句中 是的,读了你对代码的描述我很困惑,但编译器不会。与人类不同,编译器在嵌套循环方面没有问题。你不会把它混淆,因为它写了有效的C++代码,除非编译器有bug,在这种情况下,所有的赌注都被关闭了。不管有多少嵌套,如果逻辑结果是跳出整个块,那么编译器将发出这样做的代码,就像您自己编写了一个goto一样。如果您试图编写自己的goto,则由于作用域问题,更可能得到次优代码,这将要求编译器发出保存局部变量、调整堆栈帧等的代码。有许多标准优化通常在循环上执行,然而,如果你在这个循环中抛出一个goto,那么它要么是不可能的,要么就是根本不被应用 此外,即使跳过其他指令,跳转也不会总是导致更快的代码。在许多情况下,在现代、高度流水线化、无序的处理器上,预测失误的分支会造成严重的损失,远远超过直接执行干预指令并丢弃结果(忽略)的情况。针对特定平台的优化编译器知道这一点,并可能决定为其发出无分支代码
gdb -batch -ex 'file a.out' -ex 'disassemble sum'
double calcsum( double* val, unsigned int count )
{
double sum = 0;
for ( unsigned int j=0; j<count; ++count ) {
sum += val[j];
}
return sum;
}
clang++ -S -emit-llvm -O3 test.cpp # will create test.ll
$ cat test.ll
define double @_Z7calcsumPdj(double* nocapture readonly, i32)
local_unnamed_addr #0 {
%3 = icmp eq i32 %1, 0
br i1 %3, label %26, label %4
; <label>:4: ; preds = %2
%5 = load double, double* %0, align 8, !tbaa !1
%6 = sub i32 0, %1
%7 = and i32 %6, 7
%8 = icmp ugt i32 %1, -8
br i1 %8, label %12, label %9
; <label>:9: ; preds = %4
%10 = sub i32 %6, %7
br label %28
; <label>:11: ; preds = %28
br label %12
; <label>:12: ; preds = %11, %4
%13 = phi double [ undef, %4 ], [ %38, %11 ]
%14 = phi double [ 0.000000e+00, %4 ], [ %38, %11 ]
%15 = icmp eq i32 %7, 0
br i1 %15, label %24, label %16
; <label>:16: ; preds = %12
br label %17
; <label>:17: ; preds = %17, %16
%18 = phi double [ %14, %16 ], [ %20, %17 ]
%19 = phi i32 [ %7, %16 ], [ %21, %17 ]
%20 = fadd double %18, %5
%21 = add i32 %19, -1
%22 = icmp eq i32 %21, 0
br i1 %22, label %23, label %17, !llvm.loop !5
; <label>:23: ; preds = %17
br label %24
; <label>:24: ; preds = %12, %23
%25 = phi double [ %13, %12 ], [ %20, %23 ]
br label %26
; <label>:26: ; preds = %24, %2
%27 = phi double [ 0.000000e+00, %2 ], [ %25, %24 ]
ret double %27
; <label>:28: ; preds = %28, %9
%29 = phi double [ 0.000000e+00, %9 ], [ %38, %28 ]
%30 = phi i32 [ %10, %9 ], [ %39, %28 ]
%31 = fadd double %29, %5
%32 = fadd double %31, %5
%33 = fadd double %32, %5
%34 = fadd double %33, %5
%35 = fadd double %34, %5
%36 = fadd double %35, %5
%37 = fadd double %36, %5
%38 = fadd double %37, %5
%39 = add i32 %30, -8
%40 = icmp eq i32 %39, 0
br i1 %40, label %11, label %28
}