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の一部です。