Go Wiki: 目标特定代码
有时,出于性能或兼容性原因,需要为特定的 GOARCH 和 GOOS 目标编写自定义代码。本页介绍了在这种情况下应遵循的一些最佳实践。自 2019 年 4 月起,这是加密包的强制性策略。
-
尽量减少在标记文件中编写代码。应尽可能多地为所有目标构建代码。特别是,通用 Go 实现也必须为具有优化实现的那些目标构建。这对于将优化代码与通用 Go 进行比较测试至关重要,并且可以更快地发现一些构建失败。链接器将从最终二进制文件中删除未使用的代码。
-
为文件加上其目标标记命名,例如
poly1305_amd64.go
。请记住,如果一个文件以_$GOARCH.go
结尾,那就算作一个构建标记。_noasm.go
也是一个不错的后缀。 -
在标记文件中不要包含导出的函数。导出的函数定义了公共 API 及其文档,这些必须在所有目标上保持一致。如果在每个目标特定的文件中重复导出的函数,很容易导致它们不同步。中间堆栈内联器可能会处理掉一些性能成本。
-
测试所有可用实现。在具有优化实现的特定目标上运行
go test
应该同时测试通用代码和优化代码。您可以使用子测试来实现这一点。理想情况下,也包括基准测试。 -
编写对比测试。应该有一个测试,针对随机输入或边缘情况输入运行两个实现,并比较结果。随着 #19109 的进展,这些应该成为模糊测试。
提示:您可以通过运行 e.g. 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/poly1305 和 x/crypto/salsa20/salsa 包。
此内容是 Go Wiki 的一部分。