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