Go Wiki:目标特定代码

有时,出于性能或兼容性原因,需要为特定的 GOARCH 和 GOOS 目标编写自定义代码。此页面介绍了在这种情况下采用的部分最佳实践。自 2019 年 4 月起,这是加密软件包的必要策略。

  1. 最小化标记文件中的代码。尽可能多的代码应针对每个目标进行构建。特别是,通用 Go 实现也必须针对具有优化实现的目标进行构建。这对于针对通用 Go 测试优化代码至关重要,并且可以更快速地发现某些构建失败。链接器将从最终二进制文件中删除未使用的代码。

  2. 根据其标签命名文件,例如 poly1305_amd64.go。请记住,如果一个文件以 _$GOARCH.go 结尾,则它算作一个构建标签。_noasm.go 也是一个不错的后缀。

  3. 在标记文件中没有导出的函数。导出的函数定义公共 API 及其文档,它们在所有目标中必须相同。在每个特定于目标的文件中重复导出函数,可能会导致它们不同步。中间栈内联程序可能会处理一些性能成本。

  4. 测试所有可用的实现。在具有优化实现的目标上运行 go test 应该同时测试通用代码和优化代码。你可以为此使用子测试。理想情况下,基准测试也是如此。

  5. 编写比较测试。应该有一个测试,针对随机或边缘输入运行这两个实现,并比较结果。随着 #19109 的进展,这些应该成为模糊测试。

提示:你可以通过运行例如 GOARCH=arm64 go test -c 轻松地测试你的代码和测试是否针对目标编译。

示例

poly1305.go

package poly1305

// Sum generates an authenticator for m using a one-time key and puts the
// 16-byte result into out. Authenticating two different messages with the same
// key allows an attacker to forge messages at will.
func Sum(out *[16]byte, m []byte, key *[32]byte) {
    sum(out, m, key)
}

func sumGeneric(out *[16]byte, m []byte, key *[32]byte) {
    // ...
}

poly1305_amd64.go

//go:build !purego

package poly1305

//go:noescape
func sum(out *[16]byte, m []byte, key *[32]byte)

poly1305_amd64.s

//go:build !purego

// func sum(out *[16]byte, m []byte, key *[32]byte)
TEXT ·sum(SB), $0-128
    // ...

poly1305_noasm.go

//go:build !amd64 || purego

package poly1305

func sum(out *[16]byte, m []byte, key *[32]byte) {
    sumGeneric(out, m, key)
}

poly1305_test.go

package poly1305

import "testing"

func testSum(t *testing.T, sum func(tag *[16]byte, msg []byte, key *[32]byte)) {
    // ...
}

func TestSum(t *testing.T) {
    t.Run("Generic", func(t *testing.T) { testSum(t, sumGeneric) })
    t.Run("Native", func(t *testing.T) { testSum(t, sum) })
}

// TestSumCompare checks the output of sum against sumGeneric.
func TestSumCompare(t *testing.T) {
    // ...
}

有关更完整的示例,请参阅 x/crypto/poly1305x/crypto/salsa20/salsa 包。


此内容是 Go Wiki 的一部分。