Golang中Plan9性能为何不如Go代码的困惑

Golang中Plan9性能为何不如Go代码的困惑 Go代码片段:

func tryAdd(seconds, servid int64) int64
func tryAdd2(seconds, servid int64) int64 {
     return seconds + servid
}

Plan9代码片段:

TEXT ·tryAdd(SB), NOSPLIT, $0-24
     MOVQ x+0(FP), BX
     MOVQ y+8(FP), BP
     ADDQ BP, BX
     MOVQ BX, ret+16(FP)
     RET

压力测试结果显示,tryAdd2(go版本)比tryAdd(plan9版本)快近4倍 BenchmarkTryAdd-4 2000000000 1.34 ns/op BenchmarkTryAdd2-4 2000000000 0.24 ns/op


更多关于Golang中Plan9性能为何不如Go代码的困惑的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

非常感谢您的回复!
确实是内联的原因,在关闭内联后它们就会变得相同。

更多关于Golang中Plan9性能为何不如Go代码的困惑的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


也许在Go版本中加法运算被优化掉了。我找到了这个链接 https://rakyll.org/go-tool-flags/ 尝试在没有优化的情况下运行代码?

$ go build -gcflags
用于向Go编译器传递标志。go tool compile -help 列出所有可以传递给编译器的标志。

例如,要禁用编译器优化和内联,可以使用以下gcflags参数。

$ go build -gcflags="-N -l"

从性能测试结果来看,Plan9汇编版本的tryAdd确实比Go版本的tryAdd2慢很多。这主要有几个原因:

1. 函数调用开销

Plan9汇编版本需要处理完整的函数调用约定,包括参数传递和返回值设置,而Go编译器对内联的Go函数有更好的优化。

// Go编译器可能会将tryAdd2完全内联
func BenchmarkTryAdd2(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = tryAdd2(100, 200) // 可能被内联优化
    }
}

2. 寄存器分配优化

现代Go编译器在寄存器分配方面比手写汇编更智能:

// Go编译器生成的汇编可能更高效
// 可能会直接在寄存器中操作,避免内存访问

3. 汇编代码优化空间

你的Plan9汇编代码有优化空间:

TEXT ·tryAddOptimized(SB), NOSPLIT, $0-24
    MOVQ    x+0(FP), AX    // 使用AX寄存器,更通用
    ADDQ    y+8(FP), AX    // 直接加到AX
    MOVQ    AX, ret+16(FP) // 设置返回值
    RET

4. 编译器内联优势

Go版本的关键优势在于内联:

// 在实际使用中,编译器可能会这样优化:
result := a + b  // 直接替换函数调用

性能对比示例

// 测试显示内联的巨大优势
func benchmarkComparison() {
    // tryAdd2的调用可能被完全优化掉
    // 而tryAdd的汇编调用必须保留完整的函数调用框架
}

在现代Go版本中,除非有非常特定的优化需求,否则纯Go代码通常比手写汇编性能更好,因为编译器能够进行全局优化和内联处理。只有在极少数需要特定CPU指令或微优化的情况下,手写汇编才有意义。

回到顶部