【问题标题】:Why is this simple loop faster in Go than in C?为什么这个简单的循环在 Go 中比在 C 中更快?
【发布时间】:2014-11-26 21:02:42
【问题描述】:

我试图找出 Go 的循环性能是否与 C 一样好,但意外地发现,对于我的简单测试,C 版本的时间是 Go 版本的两倍。

C版:

#include <stdio.h>

int main() {
  int i = 0, a = 0;

  while (i < 1e9) {
    a = (a + i) % 42;
    i = i + 1;
  }
  printf("%d\n", a);
}

,

$ gcc -o main main.c && time ./main # tried -O0 as well; the result is similar
36
./main  10.53s user 0.08s system 98% cpu 10.769 total

Go 版本:

package main

import "fmt"

func main() {
    a := int32(0)
    for i := int32(0); i < 1e9; i++ {
        a = (a + i) % 42
    }
    fmt.Println(a)
}

,

$ time go run main.go
36
colorgo run main.go  5.27s user 0.14s system 93% cpu 5.816 total

(在达尔文测试,amd64)

对于这个简单的算法,它们不应该产生几乎相同的机器代码吗?这是由于编译器优化吗?缓存效率?

请帮我理解!谢谢!

【问题讨论】:

  • 您的 C 程序在编译时没有进行任何优化。 Go 编译器可能有不同的默认值并在没有被指示的情况下进行优化。与gcc -O2 编译以获得更公平的比较。
  • @delnan For go:“[Go] 编译器生成的代码默认是‘优化’的:” 来源:plan9.bell-labs.com/sys/doc/comp.pdf via golang.org/cmd/gc
  • 您可以获取 asm 供两者进行比较(对于 Go,它类似于 go build -gcflags -S)。我很困惑,但是您的 Go 使用的是 32 位整数,而 C 可能使用的是 64,也许 64 位除法是一条慢得多的指令。或者 Go 在这里有一些优化,在 gcc 的默认优化级别上没有启用。
  • 你为什么要比较不同的代码???您至少应该对两者使用相同的for 或while。

标签: c go benchmarking compiler-optimization computer-architecture


【解决方案1】:

优化的时间差不多。例如,

去:

$ cat t.go
package main

import "fmt"

func main() {
    a := int32(0)
    for i := int32(0); i < 1e9; i++ {
        a = (a + i) % 42
    }
    fmt.Println(a)
}
$ go version
go version devel +e1a081e6ddf8 Sat Sep 27 11:56:54 2014 -0700 linux/amd64
$ go build t.go && time ./t
36
real    0m15.809s
user    0m15.815s
sys 0m0.061s

C:

$ cat t.c
#include <stdio.h>

int main() {
  int i = 0, a = 0;

  while (i < 1e9) {
    a = (a + i) % 42;
    i = i + 1;
  }
  printf("%d\n", a);
}
$ gcc --version
gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
$ gcc -O3 t.c && time ./a.out
36
real    0m16.538s
user    0m16.528s
sys 0m0.021s

【讨论】:

  • 即使在 i7 上使用gcc -O3 -march=native,Go 仍然(只是一点点)更快。+-
【解决方案2】:

这一切都归结为生成的程序集。

go tool 6g -S(21条指令):

MOVL    $0,SI
MOVL    SI,"".a+8(FP)
MOVL    $0,CX
CMPL    CX,$1000000000
JGE     $0,58
ADDL    CX,SI
MOVL    $818089009,BP
MOVL    SI,AX
IMULL   BP,
MOVL    DX,BX
SARL    $3,BX
MOVL    SI,BP
SARL    $31,BP
SUBL    BP,BX
IMULL   $42,BX
SUBL    BX,SI
MOVL    SI,"".a+8(FP)
INCL    ,CX #point A
NOP     ,
CMPL    CX,$1000000000
JLT     $0,16
RET     ,

gcc -O3 -march=native -S(17条指令):

leal    (%rsi,%rcx), %edi
addl    $1, %ecx
vxorpd  %xmm0, %xmm0, %xmm0
vcvtsi2sd       %ecx, %xmm0, %xmm0
movl    %edi, %eax
imull   %r8d
movl    %edi, %eax
sarl    $31, %eax
sarl    $3, %edx
movl    %edx, %esi
subl    %eax, %esi
imull   $42, %esi, %esi
subl    %esi, %edi
vucomisd        %xmm0, %xmm1
movl    %edi, %esi
ja      .L2
subq    $8, %rsp

gcc -O3 -march=native -S(14条指令,将1e9替换为1000000000后):

leal    (%rdx,%rcx), %esi
addl    $1, %ecx
movl    %esi, %eax
imull   %edi
movl    %esi, %eax
sarl    $31, %eax
sarl    $3, %edx
subl    %eax, %edx
imull   $42, %edx, %edx
subl    %edx, %esi
movl    %esi, %edx
cmpl    $1000000000, %ecx
jne     .L2
subq    $8, %rsp

时间:

$ gcc -O3 -march=native loop.c; and time ./a.out
36
2.92user 0.00system 0:02.93elapsed 99%CPU
$ go build -o loop loop.go; and time ./loop
36
2.89user 0.00system 0:02.90elapsed 99%CPU
$ gcc -O3 -march=native loop_nofp.c; and time ./a.out
36
2.92user 0.00system 0:02.94elapsed 99%CPU (0avgtext+0avgdata 1312maxresident)

我不知道,在发布正确答案之前,我将暂时离开。

//编辑

更改用于匹配 Go 版本的 C 代码会产生不同的程序集,但时间完全相同。

int main() {
    int32_t i = 0, a = 0;
    for (i = 0; i < 1e9; i++) {
        a = (a + i) % 42;
    }
    printf("%d\n", a);
    return 0;
}

【讨论】:

  • MOVL $..., BP 指令是 go 用一系列乘法和移位替换除法(模运算符)的一部分。 GCC 在优化的 C 中做了类似的事情。
猜你喜欢
  • 1970-01-01
  • 2018-06-10
  • 2021-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-21
  • 2021-08-11
  • 2021-05-31
相关资源
最近更新 更多