Any decent C/C++ compiler will give the same code. For example, this:
int f1(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
int x = i + i;
sum = x - i;
}
return sum;
}
int f2(int n) {
int sum = 0;
int x;
for (int i = 0; i < n; i++) {
x = i + i;
sum = x - i;
}
return sum;
}
when compiled with 'gcc -O -S' gives: .file "loop.c"
.text
.globl f1
.type f1, @function
f1:
.LFB0:
.cfi_startproc
testl %edi, %edi
jle .L4
movl $0, %eax
.L3:
addl $1, %eax
cmpl %eax, %edi
jne .L3
subl $1, %eax
ret
.L4:
movl $0, %eax
ret
.cfi_endproc
.LFE0:
.size f1, .-f1
.globl f2
.type f2, @function
f2:
.LFB1:
.cfi_startproc
testl %edi, %edi
jle .L9
movl $0, %eax
.L8:
addl $1, %eax
cmpl %eax, %edi
jne .L8
subl $1, %eax
ret
.L9:
movl $0, %eax
ret
.cfi_endproc
.LFE1:
.size f2, .-f2
.ident "GCC: (GNU) 7.2.0"
.section .note.GNU-stack,"",@progbits
Even when compiled without optimization, the generated assembly was the same, though it was messier so I don't show it.Okay cool, so Go seems to be standing alone here without that optimization :-/
Can someone please call Rob Pike? We are discussing Golang 2.0 already and still don't have 'common' compiler optimizations... ;-) joke
I suspect llgo and gccgo do this already
Declaring a primitive variable inside vs outside a loop in c or c++ will have no effect when compiled with optimizations on by any major compiler. For non-primitive types it will depend on a number of factors, and the better choice for perf could go either way.
Yes, because if the loop is not always-executing, it may be cheaper to not initialize the complex object, even if it is safe to so.
I've also (recently) had poor performance on MSVC when "outside the loop" meant "a function argument", for a type that was essentially a struct wrapping a single primitive. It ended up being ABI weirdness that was confusing MSVC (and forceinline ended up fixing it).
What go compiler are you using? On my machine those run in exactly the same amount of time (go version go1.9 linux/amd64
).
$ ./tmp.test -test.bench .
goos: linux
goarch: amd64
BenchmarkInLoop-4 2000000000 0.46 ns/op
BenchmarkOutLoop-4 2000000000 0.46 ns/op
PASS
(pprof) disasm fast
Total: 1.95s
ROUTINE ======================== _/tmp.fast
970ms 970ms (flat, cum) 49.74% of Total
. . 4ed7c0: MOVQ 0x8(SP), AX ;main_test.go:23
. . 4ed7c5: XORL CX, CX
. . 4ed7c7: MOVQ CX, DX
. . 4ed7ca: JMP 0x4ed7d6 ;main_test.go:26
660ms 660ms 4ed7cc: LEAQ 0x1(CX), BX ;_/tmp.fast main_test.go:26
110ms 110ms 4ed7d0: MOVQ CX, DX
170ms 170ms 4ed7d3: MOVQ BX, CX
30ms 30ms 4ed7d6: CMPQ AX, CX
. . 4ed7d9: JL 0x4ed7cc ;main_test.go:26
. . 4ed7db: MOVQ DX, 0x10(SP) ;main_test.go:30
(pprof) disasm slow
Total: 1.95s
ROUTINE ======================== _/tmp.slow
960ms 960ms (flat, cum) 49.23% of Total
. . 4ed790: MOVQ 0x8(SP), AX ;main_test.go:14
. . 4ed795: XORL CX, CX
. . 4ed797: MOVQ CX, DX
. . 4ed79a: JMP 0x4ed7a6 ;main_test.go:16
690ms 690ms 4ed79c: LEAQ 0x1(CX), BX ;_/tmp.slow main_test.go:16
100ms 100ms 4ed7a0: MOVQ CX, DX
100ms 100ms 4ed7a3: MOVQ BX, CX
70ms 70ms 4ed7a6: CMPQ AX, CX
. . 4ed7a9: JL 0x4ed79c ;main_test.go:16
. . 4ed7ab: MOVQ DX, 0x10(SP) ;main_test.go:20Well, the assembler looks the same to me too, but I can confirm different runtimes on two different machines (fast is always faster here).
$ ./main.test -test.bench . -test.cpuprofile=test.profile
goos: linux
goarch: amd64
BenchmarkInLoop-8 2000000000 0.42 ns/op
BenchmarkOutLoop-8 2000000000 0.47 ns/op
PASS
(pprof) disasm fast
Total: 1.86s
ROUTINE ======================== command-line-arguments.fast
990ms 990ms (flat, cum) 53.23% of Total
. . 4ed7b0: MOVQ 0x8(SP), AX ;mem_test.go:16
. . 4ed7b5: XORL CX, CX
. . 4ed7b7: MOVQ CX, DX
. . 4ed7ba: JMP 0x4ed7c6 ;mem_test.go:19
650ms 650ms 4ed7bc: LEAQ 0x1(CX), BX ;command-line-arguments.fast mem_test.go:19
. . 4ed7c0: MOVQ CX, DX ;mem_test.go:19
10ms 10ms 4ed7c3: MOVQ BX, CX ;command-line-arguments.fast mem_test.go:19
330ms 330ms 4ed7c6: CMPQ AX, CX
. . 4ed7c9: JL 0x4ed7bc ;mem_test.go:19
. . 4ed7cb: MOVQ DX, 0x10(SP) ;mem_test.go:23
(pprof) disasm slow
Total: 1.86s
ROUTINE ======================== command-line-arguments.slow
870ms 870ms (flat, cum) 46.77% of Total
. . 4ed780: MOVQ 0x8(SP), AX ;mem_test.go:7
. . 4ed785: XORL CX, CX
. . 4ed787: MOVQ CX, DX
. . 4ed78a: JMP 0x4ed796 ;mem_test.go:9
560ms 560ms 4ed78c: LEAQ 0x1(CX), BX ;command-line-arguments.slow mem_test.go:9
. . 4ed790: MOVQ CX, DX ;mem_test.go:9
20ms 20ms 4ed793: MOVQ BX, CX ;command-line-arguments.slow mem_test.go:9
290ms 290ms 4ed796: CMPQ AX, CX
. . 4ed799: JL 0x4ed78c ;mem_test.go:9
. . 4ed79b: MOVQ DX, 0x10(SP) ;mem_test.go:13 $ go version
go version go1.9.1 linux/amd64
In Go on the other hand, doing simple 'mistakes' like declaring a variable inside a loop can bring you to performance hell:
Declaration in a loop:
Declaration outside the loop: Benchmark results: And the same is certainly true for C/C++.Src: https://nopaste.xyz/?68c6800acd9200d6#mDstI36uBPBU4Td8k//GNC...