Go编译器是否足够智能以进行微优化?

I am curious whether it makes sense to use microoptimizations like

  • a / 2 versus a >> 1 when a is an integer
  • a * 2 vs a << 1
  • a % 2 vs a & 1
  • and some others like these

I know that any decent C compiler is good enough handle this. Also please do not write about premature optimization, because these techniques are so obvious, that it is not even optimization and more like a matter of preferences how to write code.

P.S. I tried to do benchmarks and the difference in timing is not statistically significant. I do not know how to check go's bytecode so thank you for pointing it.

Short answer, yes, the compiler optimises those. But it does so slightly differently for int vs uint (and presumably any signed vs unsigned integer types such as byte).

In both cases multiplication and division instructions are avoided but it's only a single instruction for unsigned integers (and a small number of instructions for signed integers). That's because your pairs of statments are only exactly equivalent for unsigned integers and not for signed integers.

Longer answer:

Taking a simple program like:

package main

func main() {}

func div2(a int) {
        b := a / 2
        c := a >> 1
        _, _ = b, c
}

func mul2(a int) {
        b := a * 2
        c := a << 1
        _, _ = b, c
}

func mod2(a int) {
        b := a % 2
        c := a & 1
        _, _ = b, c
}

and running go build -gcflags="-S" will give you assembly output such as:

"".mod2 t=1 size=32 value=0 args=0x8 locals=0x0
        0x0000 00000 (…/opt.go:17)       TEXT    "".mod2+0(SB),4,$0-8
        …
        0x0000 00000 (…/opt.go:17)       MOVQ    "".a+8(FP),BX
        …
        0x0005 00005 (…/opt.go:18)       MOVQ    BX,AX
        0x0008 00008 (…/opt.go:18)       SARQ    $63,AX
        0x000c 00012 (…/opt.go:18)       MOVQ    BX,DX
        0x000f 00015 (…/opt.go:18)       SUBQ    AX,DX
        0x0012 00018 (…/opt.go:18)       ANDQ    $1,DX
        0x0016 00022 (…/opt.go:18)       ADDQ    AX,DX
        0x0019 00025 (…/opt.go:19)       ANDQ    $1,BX
        0x001d 00029 (…/opt.go:21)       RET     ,

Here BX is the argument and DX and BX appear to be the two results (BX being reused as one of the results). Here they are slightly different, but only by a few instructions (look at the source line numbers shown) and without any division or multiplication instructions (so basically just as fast). The difference is due to algorithmic vs logical shifts and how Go does mod for negative values.

You can confirm this by changing int to uint in the program and then the output contains things like:

        0x0008 00008 (…/opt.go:18)       ANDQ    $1,CX
        0x000c 00012 (…/opt.go:19)       ANDQ    $1,BX

i.e. the exact same instruction. This is true for each of the examples you gave.