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

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

このコミットは、Go言語の初期のコンパイラである6gにおける特定の制限によって強制されていた、バイトスライスから文字列への変換方法をより直接的で効率的な形式に修正するものです。具体的には、string(b)[0:n]という形式をstring(b[0:n])に置き換えています。

変更されたファイルは以下の通りです。

  • src/lib/bufio_test.go
  • src/lib/fmt/format.go
  • src/lib/syscall/errstr_darwin.go
  • src/lib/syscall/errstr_linux.go

コミット

  • コミットハッシュ: b80fdd1e3beec5d70e3a7bd2bdf3bdd7153c38a3
  • 作者: Russ Cox rsc@golang.org
  • コミット日時: Mon Apr 6 21:14:38 2009 -0700

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

https://github.com/golang/go/commit/b80fdd1e3beec5d70e3a7bd2bdf3bdd7153c38a3

元コミット内容

an early 6g limitation forced the use of
        string(b)[0:n]
instead of the more direct string(b[0:n]).
convert to the more direct form.

R=r
DELTA=5  (0 added, 0 deleted, 5 changed)
OCL=27082
CL=27140

変更の背景

このコミットの背景には、Go言語の初期のコンパイラである6g(Plan 9 CコンパイラをGo向けに改変したもの)における特定の技術的制限が存在しました。

Go言語では、バイトスライス([]byte)を文字列(string)に変換する際に、string(b)のように型変換を行うことができます。また、スライス操作を使って部分的なバイトスライスを取り出すことも一般的です。例えば、b[0:n]はバイトスライスbの先頭からnバイト目までの部分スライスを生成します。

通常、バイトスライスbの特定の部分を文字列として扱いたい場合、最も直接的で効率的な方法はstring(b[0:n])と記述することです。これは、まずbから必要な部分スライスb[0:n]を生成し、その部分スライスを直接文字列に変換するという操作になります。この方法では、不要な中間オブジェクトの生成やメモリコピーを避けることができます。

しかし、コミットメッセージによると、初期の6gコンパイラには「string(b[0:n])というより直接的な形式の代わりに、string(b)[0:n]の使用を強制する制限があった」とされています。これは、当時の6gコンパイラがstring(b[0:n])の形式を正しく処理できなかったか、あるいは非常に非効率的に処理していた可能性を示唆しています。そのため、開発者は一時的にstring(b)でバイトスライス全体を文字列に変換し、その後にその文字列をスライスして必要な部分を取り出すという、回りくどいstring(b)[0:n]という形式を使わざるを得ませんでした。

このコミットは、その6gコンパイラの制限が解消されたか、あるいはより良い解決策が見つかったため、コードベース全体でこの非効率な回避策をより直接的で推奨される形式に修正することを目的としています。これにより、コードの可読性が向上し、潜在的なパフォーマンスの改善やメモリ使用量の削減が期待されます。

前提知識の解説

Go言語における文字列とバイトスライス

Go言語において、string型は不変(immutable)なバイトのシーケンスであり、通常はUTF-8エンコードされたテキストを表します。一方、[]byte(バイトスライス)は可変(mutable)なバイトのシーケンスです。

  • string(b): バイトスライスbを文字列に変換します。この操作は、bの内容を新しい文字列としてコピーすることが一般的です。
  • []byte(s): 文字列sをバイトスライスに変換します。これも通常、sの内容を新しいバイトスライスとしてコピーします。

Go言語のスライス操作

Goのスライスは、配列の一部を参照するための軽量なデータ構造です。a[low:high]という形式で、配列や他のスライスから新しいスライスを作成できます。この操作は、元のデータの一部を「ビュー」として提供するものであり、通常はデータのコピーを伴いません。

例:

b := []byte{'H', 'e', 'l', 'l', 'o'}
subSlice := b[0:3] // subSlice は {'H', 'e', 'l'} を参照する

string(b)[0:n]string(b[0:n]) の違い

このコミットの核心は、これら2つの表現の違いにあります。

  1. string(b)[0:n]:

    • まず、バイトスライスb全体がstring(b)によって新しい文字列に変換されます。この時点で、bの全内容がコピーされる可能性があります。
    • 次に、その新しく生成された文字列に対して[0:n]というスライス操作が行われ、その文字列の先頭からn文字目までの部分文字列が生成されます。これもまた、新しい文字列の生成とコピーを伴う可能性があります。
    • 結果として、元のバイトスライスbのサイズによっては、不要なメモリ割り当てとコピーが2回発生する可能性があります。
  2. string(b[0:n]):

    • まず、バイトスライスbからb[0:n]という部分スライスが生成されます。この操作は、通常、元のバイトスライスのメモリを共有するため、データのコピーは発生しません。
    • 次に、その部分スライスb[0:n]が直接string(...)によって文字列に変換されます。この時点で、必要な部分のバイトのみが新しい文字列としてコピーされます。
    • 結果として、メモリ割り当てとコピーは1回で済み、より効率的です。

Goの初期コンパイラ 6g

6gは、Go言語の初期開発段階で使用されていたコンパイラの一つです。Goは元々、Rob Pike、Ken Thompson、Robert GriesemerによってGoogleで開発が始まり、その初期にはPlan 9のツールチェイン(特にCコンパイラ)をベースにしていました。6gは、x86-64アーキテクチャ向けのGoコンパイラを指します。

初期のコンパイラは、最適化が不十分であったり、特定のコードパターンでバグがあったり、あるいは特定の言語機能の実装に制限があったりすることがよくあります。このコミットで言及されている「6gの制限」は、まさにそのような初期のコンパイラの未成熟な部分に起因するものでした。

技術的詳細

このコミットは、Go言語のコードベースにおける文字列変換の最適化と、初期コンパイラ6gの制限への対応を示しています。

前述の通り、string(b)[0:n]string(b[0:n])は、結果として同じ文字列を得るかもしれませんが、その内部的な処理とパフォーマンス特性は大きく異なります。

  • string(b)[0:n]の非効率性:

    1. string(b): バイトスライスb全体を新しい文字列に変換します。この操作は、bの全内容をヒープにコピーする可能性があります。例えば、bが1MBのデータを持っていた場合、1MBの文字列が生成されます。
    2. [0:n]: その1MBの文字列から、先頭からnバイト(文字)の部分文字列を抽出します。この部分文字列もまた、新しい文字列としてヒープにコピーされる可能性があります。 このように、最大で2回のメモリ割り当てとデータコピーが発生する可能性があり、特にbが大きい場合や、この操作が頻繁に行われる場合には、パフォーマンス上のボトルネックやメモリフットプリントの増大につながります。
  • string(b[0:n])の効率性:

    1. b[0:n]: バイトスライスbの先頭からnバイトまでの部分スライスを生成します。この操作は、元のバイトスライスの基盤となる配列への参照を調整するだけであり、データのコピーは発生しません。非常に軽量な操作です。
    2. string(...): その部分スライス(b[0:n])を直接新しい文字列に変換します。この操作では、必要なnバイトのデータのみがヒープにコピーされ、新しい文字列が生成されます。 この方法では、メモリ割り当てとデータコピーは1回で済み、必要なデータ量(nバイト)に限定されます。これにより、パフォーマンスが向上し、メモリ使用量が削減されます。

このコミットは、6gコンパイラの制限が解消されたことで、より効率的なstring(b[0:n])形式への移行が可能になったことを示しています。これは、Go言語のランタイムや標準ライブラリのコードが、より洗練され、最適化されていく過程の一部です。このような小さな変更の積み重ねが、Goプログラム全体のパフォーマンスと効率に貢献します。

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

このコミットでは、以下の4つのファイルでstring(b)[i:j]のようなパターンがstring(b[i:j])に修正されています。

src/lib/bufio_test.go

--- a/src/lib/bufio_test.go
+++ b/src/lib/bufio_test.go
@@ -135,7 +135,7 @@ func readBytes(buf *BufRead) string {
 		nb++;
 	}
 	// BUG return string(b[0:nb]) ?
-	return string(b)[0:nb]
+	return string(b[0:nb])
 }

 // Call Read to accumulate the text of a file

src/lib/fmt/format.go

--- a/src/lib/fmt/format.go
+++ b/src/lib/fmt/format.go
@@ -222,7 +222,7 @@ func (f *Fmt) integer(a int64, base uint, is_signed bool, digits *string) string
 		buf[i] = ' ';
 		i--;
 	}
-	return string(buf)[i+1:nByte];
+	return string(buf[i+1:nByte]);
 }

 // Fmt_d64 formats an int64 in decimal.

src/lib/syscall/errstr_darwin.go

--- a/src/lib/syscall/errstr_darwin.go
+++ b/src/lib/syscall/errstr_darwin.go
@@ -231,7 +231,7 @@ func str(val int64) string {  // do it here rather than with fmt to avoid depend
 		val /= 10;
 	}
 	buf[i] = byte(val + '0');
-	return string(buf)[i:len(buf)];
+	return string(buf[i:len(buf)]);
 }

 func Errstr(errno int64) string {

src/lib/syscall/errstr_linux.go

--- a/src/lib/syscall/errstr_linux.go
+++ b/src/lib/syscall/errstr_linux.go
@@ -281,7 +281,7 @@ func str(val int64) string {  // do it here rather than with fmt to avoid depend
 		val /= 10;
 	}
 	buf[i] = byte(val + '0');
-	return string(buf)[i:len(buf)];
+	return string(buf[i:len(buf)]);
 }

 func Errstr(errno int64) string {

コアとなるコードの解説

上記の変更箇所はすべて、バイトスライスb(またはbuf)から部分文字列を生成する際に、string(b)[start:end]という形式をstring(b[start:end])という形式に置き換えています。

  • src/lib/bufio_test.goreadBytes 関数: このテスト関数では、バイトスライスbから読み込んだバイト列を文字列として返す際に、string(b)[0:nb]string(b[0:nb])に修正しています。コメントに// BUG return string(b[0:nb]) ?とあることから、元々string(b[0:nb])が意図されていたものの、何らかの理由(おそらく6gの制限)でstring(b)[0:nb]が使われていたことが伺えます。この修正により、テストコードもより効率的な形式に統一されます。

  • src/lib/fmt/format.gointeger 関数: この関数は数値を文字列にフォーマットする際に、内部で利用しているバイトバッファbufから結果の文字列を生成しています。ここでもstring(buf)[i+1:nByte]string(buf[i+1:nByte])に修正され、数値フォーマット処理における文字列変換の効率が改善されます。

  • src/lib/syscall/errstr_darwin.go および src/lib/syscall/errstr_linux.gostr 関数: これらのファイルは、システムコールエラーコードを文字列に変換するsyscallパッケージの一部です。str関数は数値を文字列に変換するヘルパー関数であり、ここでもバイトバッファbufから文字列を生成する際にstring(buf)[i:len(buf)]string(buf[i:len(buf)])に修正されています。これにより、エラー文字列の生成処理におけるパフォーマンスが向上します。

これらの変更は、Go言語の標準ライブラリの様々な箇所で、初期のコンパイラ制限による非効率なコードが、より直接的で効率的なコードに置き換えられたことを示しています。これは、Go言語の成熟と最適化の継続的な取り組みの一環です。

関連リンク

  • Go言語の公式ドキュメント: https://go.dev/doc/
  • Go言語の文字列とバイトスライスに関するブログ記事やドキュメント(一般的な情報源として)

参考にした情報源リンク

  • Go言語の初期の歴史やコンパイラに関する一般的な知識
  • Go言語の文字列とスライスの操作に関する一般的なドキュメントやチュートリアル
  • Go言語のソースコード(このコミット自体)
  • Go言語のIssueトラッカーやメーリングリスト(特定の6gの制限に関する詳細な議論が見つかる可能性)
    • (今回の解説では特定のIssueやメーリングリストのリンクは特定していませんが、実際の調査ではこれらの情報源が役立ちます。)