Go

release模式 编译为x86-64

我不知道Go除了使用 go build file.go 是否还有其它命令才能真正编译为release模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func setBit1(reg *uint32, value uint16) {
	for i := 0; i <= 15; i++ {
		bitToSet := ((value >> i) & 1) != 0
		*reg &= ^(1 << (i + 5))
		if bitToSet {
			*reg |= 1 << (i + 5)
		} else {
			*reg &= ^(1 << (i + 5))
		}
	}
}

它生成的汇编是什么样的呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
        TEXT    main.setBit1(SB), NOSPLIT|ABIInternal, $8-16
        SUBQ    $8, SP
        MOVQ    BP, (SP)
        LEAQ    (SP), BP
        FUNCDATA        $0, gclocals·wgcWObbY2HYnK2SU/U22lA==(SB)
        FUNCDATA        $1, gclocals·J5F+7Qw7O7ve2QcWC7DpeQ==(SB)
        FUNCDATA        $5, main.setBit1.arginfo1(SB)
        FUNCDATA        $6, main.setBit1.argliveinfo(SB)
        PCDATA  $3, $1
        XORL    CX, CX
        JMP     main_setBit1_pc22
main_setBit1_pc16:
        LEAQ    1(R9), CX
        MOVL    DX, BX
main_setBit1_pc22:
        CMPQ    CX, $15
        JGT     main_setBit1_pc103
        MOVL    BX, DX
        SHRW    CX, BX
        MOVL    (AX), SI
        LEAQ    5(CX), DI
        TESTQ   DI, DI
        JLT     main_setBit1_pc112
        CMPQ    DI, $32
        SBBL    R8, R8
        MOVQ    CX, R9
        MOVQ    DI, CX
        MOVL    $1, R10
        SHLL    CX, R10
        ANDL    R8, R10
        MOVL    R10, DI
        NOTL    R10
        ANDL    SI, R10
        MOVL    R10, (AX)
        TESTW   $1, BX
        JEQ     main_setBit1_pc98
        ORL     DI, R10
        MOVL    R10, (AX)
        NOP
        JMP     main_setBit1_pc16
main_setBit1_pc98:
        MOVL    R10, (AX)
        JMP     main_setBit1_pc16
main_setBit1_pc103:
        MOVQ    (SP), BP
        ADDQ    $8, SP
        RET
main_setBit1_pc112:
        PCDATA  $1, $1
        CALL    runtime.panicshift(SB)
        XCHGL   AX, AX
        TEXT    main.main(SB), NOSPLIT|ABIInternal, $0-0
        FUNCDATA        $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
        FUNCDATA        $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
        RET

https://go.godbolt.org/z/79n33vMh8

可以看到Go似乎只使用了普通指令

JMP main_setBit1_pc16 会执行无限循环,直到满足条件JGT main_setBit1_pc103时才退出,也就是cpu会循环15次执行这几条指令。

这实际上不符合预期,这个循环应该可以被编译器优化掉以获得更好的性能。一般来说,CPU需要执行的指令数越少,性能通常会更好。

Rust

等效实现 编译为x86-64:

编译命令 cargo build --release

这是rust默认的release模式,实际上rust默认的release模式比较鸡肋,真正的项目中应该至少加入 lto = true codegen-units = 1两个参数,不过对于这篇文章中的小函数不影响。

1
2
3
4
5
6
7
pub fn set_bit1(reg: &mut u32, value: u16) {
    for i in 0..=15 {
        let bit_to_set = ((value >> i) & 1) != 0;
        *reg &= !(1 << (i + 5));
        *reg |= (bit_to_set as u32) << (i + 5);
    }
}

它生成的汇编为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
.LCPI0_0:
        .short  32
        .short  64
        .short  128
        .short  256
        .short  512
        .short  1024
        .short  2048
        .short  4096
example::set_bit1:
        mov     eax, -2097121
        and     eax, dword ptr [rdi]
        movd    xmm0, esi
        psllw   xmm0, 5
        pshuflw xmm0, xmm0, 0
        pshufd  xmm0, xmm0, 0
        shl     esi, 5
        mov     ecx, esi
        and     ecx, 8192
        pand    xmm0, xmmword ptr [rip + .LCPI0_0]
        pxor    xmm1, xmm1
        movdqa  xmm2, xmm0
        punpckhwd       xmm2, xmm1
        punpcklwd       xmm0, xmm1
        por     xmm0, xmm2
        pshufd  xmm1, xmm0, 238
        por     xmm1, xmm0
        pshufd  xmm0, xmm1, 85
        por     xmm0, xmm1
        movd    edx, xmm0
        mov     r8d, esi
        and     r8d, 49152
        or      r8d, ecx
        mov     ecx, esi
        and     ecx, 983040
        or      ecx, r8d
        or      ecx, edx
        and     ecx, -1048577
        or      ecx, eax
        and     esi, 1048576
        or      esi, ecx
        mov     dword ptr [rdi], esi
        ret

https://rust.godbolt.org/z/aWKEqvnoP

看起来rust会自动在支持的平台上自动使用部分高级指令,在 x86-64 中默认使用了sse2。把整个循环直接优化掉了,cpu只会执行这40行左右的代码。