Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14806] ファイルの概要

このコミットは、Go言語の標準ライブラリおよびツールにおいて、バイトスライス([]byte)の比較にbytes.Compare(a, b) == 0を使用している箇所を、より効率的で読みやすいbytes.Equal(a, b)に置き換える変更です。この変更は、gofmtツールによる自動置換によって広範囲に適用されました。

コミット

commit 46811d27ce6b3753f70bc49423f4f448e613609d
Author: Matthew Dempsky <mdempsky@google.com>
Date:   Mon Jan 7 10:03:49 2013 +1100

    src: Use bytes.Equal instead of bytes.Compare where possible.
    
    bytes.Equal is simpler to read and should also be faster because
    of short-circuiting and assembly implementations.
    
    Change generated automatically using:
      gofmt -r 'bytes.Compare(a, b) == 0 -> bytes.Equal(a, b)'
      gofmt -r 'bytes.Compare(a, b) != 0 -> !bytes.Equal(a, b)'
    
    R=golang-dev, dave, adg, rsc
    CC=golang-dev
    https://golang.org/cl/7038051

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/46811d27ce6b3753f70bc49423f4f448e613609d

元コミット内容

src: Use bytes.Equal instead of bytes.Compare where possible.

bytes.Equal is simpler to read and should also be faster because
of short-circuiting and assembly implementations.

Change generated automatically using:
  gofmt -r 'bytes.Compare(a, b) == 0 -> bytes.Equal(a, b)'
  gofmt -r 'bytes.Compare(a, b) != 0 -> !bytes.Equal(a, b)'

R=golang-dev, dave, adg, rsc
CC=golang-dev
https://golang.org/cl/7038051

変更の背景

この変更の主な背景は、Go言語におけるバイトスライスの比較処理の可読性とパフォーマンスの向上です。

Go言語のbytesパッケージには、バイトスライスを比較するための2つの主要な関数、bytes.Comparebytes.Equalがあります。

  • bytes.Compare(a, b): 2つのバイトスライスabを辞書順に比較し、a == bなら0a < bなら-1a > bなら1を返します。これは、ソートなどの順序関係を判定する際に使用されます。
  • bytes.Equal(a, b): 2つのバイトスライスabが完全に等しいかどうかを判定し、等しければtrue、そうでなければfalseを返します。

これまで、バイトスライスの等価性をチェックするためにbytes.Compare(a, b) == 0という表現が使われることがありました。しかし、これは冗長であり、bytes.Equal(a, b)というより直接的で意図が明確な関数が存在します。

このコミットは、以下の理由からbytes.Equalへの置き換えを推進しています。

  1. 可読性の向上: bytes.Equal(a, b)は、2つのバイトスライスが等しいかどうかを直接的に表現するため、コードの意図がより明確になります。
  2. パフォーマンスの向上: bytes.Equalは、等価性チェックに特化して最適化されています。特に、バイトスライスの長さが異なる場合や、先頭から比較してすぐに不一致が見つかる場合に、bytes.Compareよりも高速に処理を終了できます(ショートサーキット)。また、一部のアーキテクチャではアセンブリ言語による最適化された実装が提供されている場合があり、これもパフォーマンス向上に寄与します。

この変更は、gofmtというGo言語のコードフォーマッタの-rオプション(リライトルール)を使用して自動的に行われました。これにより、Go言語のコードベース全体で一貫した改善が効率的に適用されました。

前提知識の解説

Go言語のbytesパッケージ

bytesパッケージは、バイトスライス([]byte)を操作するためのユーティリティ関数を提供します。文字列操作におけるstringsパッケージに相当するものです。

bytes.Compare(a, b []byte) int

この関数は、2つのバイトスライスabを辞書順に比較します。

  • abより小さい場合、負の整数(通常-1)を返します。
  • abと等しい場合、0を返します。
  • abより大きい場合、正の整数(通常1)を返します。

この関数は、主にバイトスライスの順序付けが必要な場合(例: ソートアルゴリズムの比較関数)に使用されます。

bytes.Equal(a, b []byte) bool

この関数は、2つのバイトスライスabがバイト単位で完全に等しいかどうかを判定します。

  • 両者の長さが異なれば、即座にfalseを返します。
  • 長さが同じであれば、各バイトを比較し、すべてが一致すればtrue、一つでも異なればfalseを返します。

パフォーマンスの違い

bytes.Compare(a, b) == 0bytes.Equal(a, b)は、結果的に同じ等価性チェックを行いますが、内部的な実装とパフォーマンス特性が異なります。

  • bytes.Compareの挙動: bytes.Compareは、等価性だけでなく、辞書順の順序関係も判定するため、比較対象のバイトスライスが等しい場合でも、最後までバイトを比較し続ける可能性があります(実装によるが、一般的に等価性チェックに特化した関数よりオーバーヘッドが大きい)。
  • bytes.Equalの挙動: bytes.Equalは、等価性チェックに特化しているため、以下の最適化が施されています。
    • 長さのチェック: まず最初に2つのバイトスライスの長さを比較します。長さが異なれば、それ以上バイトを比較することなくfalseを返します。これは非常に高速なチェックです。
    • ショートサーキット: バイト単位の比較中に、不一致のバイトが見つかった場合、その時点で比較を中止しfalseを返します。
    • アセンブリ最適化: 多くのプラットフォームでは、bytes.Equalの内部実装に、CPUのSIMD命令などを利用したアセンブリ言語による高速な比較ルーチンが使用されています。これにより、C言語やGo言語で書かれた一般的なループによる比較よりもはるかに高速に動作します。

したがって、等価性のみをチェックする場合は、bytes.Equalを使用する方が、コードの意図が明確になるだけでなく、パフォーマンス面でも有利です。

gofmtツール

gofmtは、Go言語のソースコードを自動的にフォーマットするツールです。Go言語の標準的なコーディングスタイルに準拠させることで、コードの可読性と一貫性を高めます。

gofmtには-rオプションがあり、これを使用すると指定したリライトルールに基づいてコードを変換できます。リライトルールは、パターン -> 置換の形式で記述され、AST(抽象構文木)レベルでコードを変換します。

このコミットでは、以下のリライトルールが使用されました。

  • bytes.Compare(a, b) == 0 -> bytes.Equal(a, b)
  • bytes.Compare(a, b) != 0 -> !bytes.Equal(a, b)

これにより、コードベース全体で安全かつ効率的にbytes.Compareからbytes.Equalへの置き換えが行われました。

技術的詳細

このコミットは、Go言語の標準ライブラリ内の複数のパッケージ(cmd/gofmt, pkg/bufio, pkg/crypto/rsa, pkg/encoding/asn1, pkg/encoding/hex, pkg/encoding/json, pkg/exp/locale/collate, pkg/go/doc, pkg/math/big)にわたる変更を含んでいます。これらの変更はすべて、バイトスライスの等価性チェックをbytes.Compare(a, b) == 0またはbytes.Compare(a, b) != 0から、それぞれbytes.Equal(a, b)または!bytes.Equal(a, b)に置き換えるものです。

この変更の技術的なメリットは以下の点に集約されます。

  1. パフォーマンス最適化:

    • ショートサーキット評価: bytes.Equalは、比較対象のバイトスライスの長さが異なる場合、または比較中に最初の不一致バイトが見つかった場合、即座に結果を返します。これはbytes.Compareが辞書順比較のために、より多くのバイトを比較する必要がある場合があるのと対照的です。
    • アセンブリ言語による実装: 多くのGoのランタイム環境では、bytes.Equalは基盤となるCPUアーキテクチャに最適化されたアセンブリ言語ルーチンを利用しています。これにより、C言語やGo言語で書かれた同等のループよりもはるかに高速なバイト比較が可能になります。特に大量のバイトスライスを頻繁に比較するようなケースでは、この最適化が顕著な性能向上をもたらします。
    • コンパイラの最適化の機会: bytes.Equalのような特化された関数は、コンパイラがより積極的な最適化(例: インライン化)を行う機会を提供し、結果として生成される機械語コードの効率を高めることができます。
  2. コードの意図の明確化:

    • bytes.Equal(a, b)は、その名前が示す通り「abが等しいか」という意図を直接的に表現します。一方、bytes.Compare(a, b) == 0は、「abの辞書順比較の結果が0であるか」という間接的な表現であり、等価性チェックという目的を読み取るには一歩思考が必要です。コードの可読性は、特に大規模なプロジェクトやチーム開発において、バグの発見やメンテナンスの容易さに直結します。
  3. gofmtによる自動化:

    • この変更が手動ではなくgofmt -rコマンドによって自動的に行われたことは、Goエコシステムにおけるツールの成熟度と、大規模なコードベース全体にわたる一貫したリファクタリングの容易さを示しています。gofmtのリライト機能は、特定のパターンに合致するコードを安全かつ網羅的に変換できるため、開発者が手動で変更を行う際の人為的ミスや見落としを防ぎます。

これらの技術的側面から、このコミットはGo言語のコードベース全体の品質とパフォーマンスを向上させるための重要な一歩と言えます。

コアとなるコードの変更箇所

このコミットでは、多数のファイルで同様の変更が行われています。以下に代表的な変更箇所をいくつか示します。

src/cmd/gofmt/gofmt_test.go

--- a/src/cmd/gofmt/gofmt_test.go
+++ b/src/cmd/gofmt/gofmt_test.go
@@ -56,7 +56,7 @@ func runTest(t *testing.T, in, out, flags string) {
 		return
 	}
 
-	if got := buf.Bytes(); bytes.Compare(got, expected) != 0 {
+	if got := buf.Bytes(); !bytes.Equal(got, expected) {
 		t.Errorf("(gofmt %s) != %s (see %s.gofmt)", in, out, in)
 		d, err := diff(expected, got)
 		if err == nil {

src/pkg/crypto/rsa/pkcs1v15_test.go

--- a/src/pkg/crypto/rsa/pkcs1v15_test.go
+++ b/src/pkg/crypto/rsa/pkcs1v15_test.go
@@ -57,7 +57,7 @@ func TestDecryptPKCS1v15(t *testing.T) {
 			t.Errorf("#%d error decrypting", i)
 		}
 		want := []byte(test.out)
-		if bytes.Compare(out, want) != 0 {
+		if !bytes.Equal(out, want) {
 			t.Errorf("#%d got:%#v want:%#v", i, out, want)
 		}
 	}
@@ -90,7 +90,7 @@ func TestEncryptPKCS1v15(t *testing.T) {
 			return false
 		}
 
-		if bytes.Compare(plaintext, in) != 0 {
+		if !bytes.Equal(plaintext, in) {
 			t.Errorf("output mismatch: %#v %#v", plaintext, in)
 			return false
 		}
@@ -132,7 +132,7 @@ func TestEncryptPKCS1v15SessionKey(t *testing.T) {
 			t.Errorf("#%d error decrypting", i)
 		}
 		want := []byte(test.out)
-		if bytes.Compare(key, want) != 0 {
+		if !bytes.Equal(key, want) {
 			t.Errorf("#%d got:%#v want:%#v", i, key, want)
 		}
 	}
@@ -176,7 +176,7 @@ func TestSignPKCS1v15(t *testing.T) {
 		}
 
 		expected, _ := hex.DecodeString(test.out)
-		if bytes.Compare(s, expected) != 0 {
+		if !bytes.Equal(s, expected) {
 			t.Errorf("#%d got: %x want: %x", i, s, expected)
 		}
 	}

src/pkg/encoding/json/scanner_test.go

--- a/src/pkg/encoding/json/scanner_test.go
+++ b/src/pkg/encoding/json/scanner_test.go
@@ -92,7 +92,7 @@ func TestCompactBig(t *testing.T) {
 		t.Fatalf("Compact: %v", err)
 	}
 	b := buf.Bytes()
-	if bytes.Compare(b, jsonBig) != 0 {
+	if !bytes.Equal(b, jsonBig) {
 		t.Error("Compact(jsonBig) != jsonBig")
 		diff(t, b, jsonBig)
 		return
@@ -118,7 +118,7 @@ func TestIndentBig(t *testing.T) {
 		t.Fatalf("Indent2: %v", err)
 	}
 	b1 := buf1.Bytes()
-	if bytes.Compare(b1, b) != 0 {
+	if !bytes.Equal(b1, b) {
 		t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)")
 		diff(t, b1, b)
 		return
@@ -130,7 +130,7 @@ func TestIndentBig(t *testing.T) {
 		t.Fatalf("Compact: %v", err)
 	}
 	b1 = buf1.Bytes()
-	if bytes.Compare(b1, jsonBig) != 0 {
+	if !bytes.Equal(b1, jsonBig) {
 		t.Error("Compact(Indent(jsonBig)) != jsonBig")
 		diff(t, b1, jsonBig)
 		return

コアとなるコードの解説

上記の変更箇所は、すべて同じパターンに従っています。

  • 変更前: bytes.Compare(sliceA, sliceB) != 0 または bytes.Compare(sliceA, sliceB) == 0
  • 変更後: !bytes.Equal(sliceA, sliceB) または bytes.Equal(sliceA, sliceB)

例えば、src/cmd/gofmt/gofmt_test.goの変更では、gofmtのテストにおいて、フォーマット後のバイトスライスgotと期待されるバイトスライスexpectedが等しくないことを確認する条件式が変更されています。

変更前:

if got := buf.Bytes(); bytes.Compare(got, expected) != 0 {

これは「gotexpectedを比較した結果が0ではない(つまり等しくない)ならば」という意味です。

変更後:

if got := buf.Bytes(); !bytes.Equal(got, expected) {

これは「gotexpectedが等しくないならば」という意味で、より直接的で読みやすくなっています。

同様に、src/pkg/crypto/rsa/pkcs1v15_test.gosrc/pkg/encoding/json/scanner_test.goなどのテストファイルでも、復号化されたデータやエンコードされたデータが期待値と一致するかどうかをチェックする際に、bytes.Compareを用いた比較からbytes.Equalを用いた比較へと置き換えられています。

これらの変更は、コードの意図をより明確にし、同時にbytes.Equalが提供するパフォーマンス上の利点(特にショートサーキットやアセンブリ最適化)を享受することを目的としています。

関連リンク

参考にした情報源リンク