Go Wiki: ターゲット固有のコード

パフォーマンスや互換性の理由から、特定の GOARCH と GOOS ターゲット向けにカスタムコードを作成する必要がある場合があります。このページでは、その場合に採用すべきベストプラクティスを紹介します。2019年4月現在、これは暗号パッケージの必須ポリシーです。

  1. タグ付きファイル内のコードを最小限に抑える。 可能な限り多くのコードがすべてのターゲットでビルドされるようにする必要があります。特に、汎用 Go 実装は、最適化された実装を持つターゲットでもビルドする必要があります。これは、最適化されたコードを汎用 Go に対してテストするために重要であり、ビルドの失敗に迅速に気付くことができます。リンカは最終的なバイナリから未使用のコードを削除します。

  2. `poly1305_amd64.go` のように、タグにちなんでファイルに名前を付ける。 ファイルが `_$GOARCH.go` で終わる場合、それはビルドタグとしてカウントされることに注意してください。 `_noasm.go` も良いサフィックスです。

  3. タグ付きファイルにエクスポートされた関数を含めない。 エクスポートされた関数は、すべてのパブリック API とそのドキュメントを定義し、これはすべてのターゲットで同じである必要があります。エクスポートされた関数を各ターゲット固有のファイルで繰り返すと、同期がとれなくなる可能性があります。ミッドスタックインライナーは、おそらくパフォーマンスコストの一部を処理します。

  4. 利用可能なすべての実装をテストする。 最適化された実装を持つターゲットで `go test` を実行すると、汎用コードと最適化されたコードの両方がテストされます。このためには、サブテストを使用できます。理想的には、ベンチマークも同様です。

  5. 比較テストを作成する。 ランダムまたはエッジ入力に対して2つの実装を実行し、結果を比較するテストが必要です。#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 の一部です。