Go Wiki: 目標特定程式碼

有時,出於效能或相容性原因,需要為特定的 GOARCH 和 GOOS 目標編寫自定義程式碼。本頁介紹了在這種情況下應遵循的一些最佳實踐。自 2019 年 4 月起,這是加密包的強制性策略。

  1. 儘量減少在標記檔案中編寫程式碼。應儘可能多地為所有目標構建程式碼。特別是,通用 Go 實現也必須為具有最佳化實現的那些目標構建。這對於將最佳化程式碼與通用 Go 進行比較測試至關重要,並且可以更快地發現一些構建失敗。連結器將從最終二進位制檔案中刪除未使用的程式碼。

  2. 為檔案加上其目標標記命名,例如 poly1305_amd64.go。請記住,如果一個檔案以 _$GOARCH.go 結尾,那就算作一個構建標記。_noasm.go 也是一個不錯的字尾。

  3. 在標記檔案中不要包含匯出的函式。匯出的函式定義了公共 API 及其文件,這些必須在所有目標上保持一致。如果在每個目標特定的檔案中重複匯出的函式,很容易導致它們不同步。中間堆疊內聯器可能會處理掉一些效能成本。

  4. 測試所有可用實現。在具有最佳化實現的特定目標上執行 go test 應該同時測試通用程式碼和最佳化程式碼。您可以使用子測試來實現這一點。理想情況下,也包括基準測試。

  5. 編寫對比測試。應該有一個測試,針對隨機輸入或邊緣情況輸入執行兩個實現,並比較結果。隨著 #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/poly1305x/crypto/salsa20/salsa 包。


此內容是 Go Wiki 的一部分。