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

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

このコミットは、Go言語の初期開発段階において、ioパッケージ内に基本的なByteBufferを導入し、既存のプロトコルバッファ実装がそれを利用するように修正したものです。これにより、バイトデータの効率的な読み書きと管理のための基盤が提供されました。

コミット

commit 5f9254c11a2d4c137149dac38f3ebf493ebce8bc
Author: Rob Pike <r@golang.org>
Date:   Tue Nov 25 09:41:58 2008 -0800

    make a (rudimentary) ByteBuffer and put it in package "io".
    fix up protocol buffers to use it.
    
    R=rsc
    DELTA=1232  (612 added, 572 deleted, 48 changed)
    OCL=19964
    CL=19981

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

https://github.com/golang/go/commit/5f9254c11a2d4c137149dac38f3ebf493ebce8bc

元コミット内容

make a (rudimentary) ByteBuffer and put it in package "io".
fix up protocol buffers to use it.

変更の背景

このコミットは、Go言語がまだ一般に公開される前の、非常に初期のプロトタイプ開発段階(2008年11月)に行われたものです。当時のGo言語には、バイトデータを効率的に扱うための標準的なバッファリングメカニズムが不足していました。特に、プロトコルバッファのようなシリアライゼーションメカニズムは、バイト列の構築と解析を頻繁に行うため、効率的なバイトバッファの存在が不可欠です。

この変更の背景には、以下のニーズがあったと考えられます。

  1. バイトデータ処理の効率化: ネットワーク通信やファイルI/O、データシリアライゼーションなど、多くの場面でバイト列の操作が必要となります。これらの操作を効率的に行うための、メモリ上でのバイトバッファリング機能が求められていました。
  2. プロトコルバッファの要件: プロトコルバッファは、構造化されたデータを効率的にシリアライズ・デシリアライズするためのGoogleのメカニズムです。Go言語でプロトコルバッファを適切にサポートするためには、その内部で利用するバイトバッファの実装が必要でした。
  3. ioパッケージの拡充: ioパッケージは、Go言語におけるI/O操作の基本的なインターフェースと機能を提供する中心的なパッケージです。バイトバッファのような基本的なデータ構造をこのパッケージに含めることで、Go言語のI/Oエコシステムの基盤を強化する意図があったと考えられます。
  4. Go言語の設計思想: Go言語は、シンプルさ、効率性、並行性を重視して設計されています。ByteBufferのような基本的なユーティリティを標準ライブラリに提供することは、開発者が共通のツールを使って効率的にコードを書けるようにするというGoの設計哲学に合致しています。

このコミットは、Go言語の標準ライブラリが形成されていく過程における、重要な一歩を示しています。

前提知識の解説

Go言語の初期開発

Go言語は、GoogleでRob Pike、Ken Thompson、Robert Griesemerによって2007年に設計が開始され、2008年にはプロトタイプの実装が進められていました。このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の、内部開発段階でした。そのため、現在のGo言語の標準ライブラリとは異なる構造や命名規則が見られることがあります。

ioパッケージ

Go言語のioパッケージは、I/Oプリミティブ(基本的な入出力操作)を提供します。ReaderWriterといったインターフェースは、Go言語におけるI/Oの抽象化の核となっています。このコミットでは、これらのインターフェースを実装する具体的なByteBufferが導入されました。

プロトコルバッファ (Protocol Buffers)

プロトコルバッファは、Googleが開発した、構造化データをシリアライズするための言語ニュートラルでプラットフォームニュートラルな拡張可能なメカニズムです。XMLやJSONに似ていますが、より小さく、より速く、よりシンプルです。データ構造を定義し、それを使って様々な言語でデータを生成・解析することができます。効率的なデータ交換のために、バイト列への変換(マーシャリング)とバイト列からの復元(アンマーシャリング)が頻繁に行われます。

Makefile

Makefileは、ソフトウェアのビルドプロセスを自動化するためのファイルです。Go言語の初期のビルドシステムでは、Makefileが広く使われていました。このコミットでは、新しいioパッケージとその中のbytebuffer.goをビルドプロセスに組み込むために、既存のsrc/lib/Makefileと新しく作成されたsrc/lib/io/Makefileが変更されています。

io.Readerio.Writerインターフェース

Go言語におけるio.Readerio.Writerは、それぞれデータの読み込みと書き込みのための基本的なインターフェースです。

  • io.Reader: Read(p []byte) (n int, err error) メソッドを持ち、バイトスライスpにデータを読み込み、読み込んだバイト数nとエラーerrを返します。
  • io.Writer: Write(p []byte) (n int, err error) メソッドを持ち、バイトスライスpのデータを書き込み、書き込んだバイト数nとエラーerrを返します。

ByteBufferは、これらのインターフェースを実装することで、他のI/O操作とシームレスに連携できるようになります。

技術的詳細

このコミットの主要な技術的変更点は、ioパッケージへのByteBufferの実装と、それに伴うビルドシステムの更新です。

ByteBufferの構造と機能

src/lib/io/bytebuffer.goで定義されているByteBuffer構造体は、以下のフィールドを持ちます。

export type ByteBuffer struct {
	buf	*[]byte; // 内部のバイトスライス
	off	int;	// 読み込み開始オフセット
	len	int;	// 書き込み済みデータの長さ(論理的な長さ)
	cap	int;    // バッファの容量(物理的な長さ)
}

このByteBufferは、以下のメソッドを提供します。

  • Reset(): バッファの読み書き位置をリセットし、論理的な長さを0にします。内部のバイトスライスは再利用されます。
  • Write(p *[]byte) (n int, err *os.Error): 引数pのバイトスライスをバッファに書き込みます。
    • バッファが初期化されていない場合、plen + 1024の容量で新しいバイトスライスが割り当てられます。
    • 書き込みによって容量が不足する場合、現在の容量の2倍(2*(b.cap + plen))の新しいバイトスライスが割り当てられ、既存のデータがコピーされます(再アロケーション)。
    • bytecopyヘルパー関数を使ってデータをコピーします。
  • Read(p *[]byte) (n int, err *os.Error): バッファから引数pのバイトスライスにデータを読み込みます。
    • バッファが空の場合、0バイトを読み込み、nilを返します。
    • 読み込み可能なデータがない場合(b.off == b.len)、Reset()を呼び出してバッファをリセットし、0バイトを読み込みます。
    • pの長さが残りのデータ長より大きい場合、残りのデータ長に合わせて読み込みます。
    • bytecopyヘルパー関数を使ってデータをコピーし、読み込みオフセットb.offを更新します。
  • Len() int: バッファに現在書き込まれているデータの論理的な長さ(b.len)を返します。
  • Data() *[]byte: バッファ内の有効なデータ部分(b.buf[b.off:b.len])をバイトスライスとして返します。
  • NewByteBufferFromArray(buf *[]byte) *ByteBuffer: 既存のバイトスライスから新しいByteBufferを作成するファクトリ関数です。

bytecopyヘルパー関数

bytecopyは、バイトスライス間でデータをコピーするためのシンプルなループベースの関数です。現在のGo言語ではcopy()組み込み関数が利用されますが、このコミット時点ではまだ存在しなかったか、あるいは内部的なヘルパーとして実装されたものと考えられます。

func bytecopy(dst *[]byte, doff int, src *[]byte, soff int, count int) {
	for i := 0; i < count; i++ {
		dst[doff] = src[soff];
		doff++;
		soff++;
	}
}

ビルドシステムの変更

  • src/lib/Makefile:
    • DIRS変数にioディレクトリが追加され、Goの標準ライブラリの一部としてioパッケージがビルド対象に含まれるようになりました。
    • FILES変数からioが削除されました。これは、io.gosrc/lib直下からsrc/lib/ioディレクトリに移動したためです。
    • bufio.6, fmt.dirinstall, http.dirinstall, tabwriter.dirinstallなどの依存関係が、io.installからio.dirinstallに変更されました。これは、ioパッケージがディレクトリとして扱われるようになったことを示唆しています。
    • io.dirinstallの定義が追加され、os.dirinstallsyscall.dirinstallに依存することが示されました。
  • src/lib/io/Makefile:
    • ioパッケージ専用の新しいMakefileが作成されました。
    • io.$Obytebuffer.$Oというオブジェクトファイルがビルドされ、それらがio.aというアーカイブライブラリにまとめられることが定義されています。
    • gotestコマンドを使ったテスト実行や、6covを使ったカバレッジ測定のターゲットも含まれています。
    • installターゲットは、ビルドされたio.aGOROOT/pkg/io.aにコピーすることで、他のパッケージから利用可能にします。

ファイルの移動

src/lib/io.gosrc/lib/io/io.goに移動しました。これは、ioパッケージが単一のファイルから、ioディレクトリ内の複数のファイル(io.gobytebuffer.go)で構成されるようになったことを意味します。これにより、パッケージ内の機能がより適切に分割され、管理しやすくなります。

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

src/lib/io/bytebuffer.go (新規ファイル)

このファイル全体が新規追加され、ByteBuffer構造体とその関連メソッドが定義されています。

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package io

// Byte buffer for marshaling nested messages.

import (
	"io";
	"os";
)

// A simple implementation of the io.Read and io.Write interfaces.
// A newly allocated ByteBuffer is ready to use.

// TODO(r): Do better memory management.

func bytecopy(dst *[]byte, doff int, src *[]byte, soff int, count int) {
	for i := 0; i < count; i++ {
		dst[doff] = src[soff];
		doff++;
		soff++;
	}
}

export type ByteBuffer struct {
	buf	*[]byte;
	off	int;	// Read from here
	len	int;	// Write to here
	cap	int;
}

func (b *ByteBuffer) Reset() {
	b.off = 0;
	b.len = 0;
}

func (b *ByteBuffer) Write(p *[]byte) (n int, err *os.Error) {
	plen := len(p);
	if b.buf == nil {
		b.cap = plen + 1024;
		b.buf = new([]byte, b.cap);
		b.len = 0;
	}
	if b.len + len(p) > b.cap {
		b.cap = 2*(b.cap + plen);
		nb := new([]byte, b.cap);
		bytecopy(nb, 0, b.buf, 0, b.len);
		b.buf = nb;
	}
	bytecopy(b.buf, b.len, p, 0, plen);
	b.len += plen;
	return plen, nil;
}

func (b *ByteBuffer) Read(p *[]byte) (n int, err *os.Error) {
	plen := len(p);
	if b.buf == nil {
		return 0, nil
	}
	if b.off == b.len {	// empty buffer
		b.Reset();
		return 0, nil
	}
	if plen > b.len - b.off {
		plen = b.len - b.off
	}
	bytecopy(p, 0, b.buf, b.off, plen);
	b.off += plen;
	return plen, nil;
}

func (b *ByteBuffer) Len() int {
	return b.len
}

func (b *ByteBuffer) Data() *[]byte {
	return b.buf[b.off:b.len]
}


export func NewByteBufferFromArray(buf *[]byte) *ByteBuffer {
	b := new(ByteBuffer);
	b.buf = buf;
	b.off = 0;
	b.len = len(buf);
	b.cap = len(buf);
	return b;
}

src/lib/Makefile (変更箇所)

ioパッケージの追加と依存関係の更新。

--- a/src/lib/Makefile
+++ b/src/lib/Makefile
@@ -11,6 +11,7 @@ DIRS=\
 	fmt\
 	hash\
 	http\
+	io\
 	math\
 	net\
 	os\
@@ -25,7 +26,6 @@ FILES=\
 	bufio\
 	vector\
 	flag\
--	io\
 	once\
 	rand\
 	sort\
@@ -81,19 +81,19 @@ test: test.files
 # TODO: dependencies - should auto-generate
 
 bignum.6: fmt.dirinstall
-bufio.6: io.install os.dirinstall
+bufio.6: io.dirinstall os.dirinstall
 flag.6: fmt.dirinstall
--io.6: os.dirinstall syscall.dirinstall
 testing.6: flag.install fmt.dirinstall
 
-fmt.dirinstall: io.install reflect.dirinstall strconv.dirinstall
+fmt.dirinstall: io.dirinstall reflect.dirinstall strconv.dirinstall
 hash.dirinstall: os.dirinstall
-http.dirinstall: bufio.install io.install net.dirinstall os.dirinstall strings.install
+http.dirinstall: bufio.install io.dirinstall net.dirinstall os.dirinstall strings.install
+io.dirinstall: os.dirinstall syscall.dirinstall
 net.dirinstall: once.install os.dirinstall strconv.dirinstall
 os.dirinstall: syscall.dirinstall
 regexp.dirinstall: os.dirinstall
 reflect.dirinstall: strconv.dirinstall
 strconv.dirinstall: os.dirinstall utf8.install
-tabwriter.dirinstall: os.dirinstall io.install container/array.dirinstall
+tabwriter.dirinstall: os.dirinstall io.dirinstall container/array.dirinstall
 time.dirinstall: once.install os.dirinstall

src/lib/io/Makefile (新規ファイル)

ioパッケージのビルド定義。

--- /dev/null
+++ b/src/lib/io/Makefile
@@ -0,0 +1,56 @@
+# Copyright 2009 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+# DO NOT EDIT.  Automatically generated by gobuild.
+# gobuild -m >Makefile
+O=6
+GC=$(O)g
+CC=$(O)c -w
+AS=$(O)a
+AR=$(O)ar
+
+default: packages
+
+clean:
+	rm -f *.$O *.a $O.out
+
+test: packages
+	gotest
+
+coverage: packages
+	gotest
+	6cov -g `pwd` | grep -v '_test\.go:'\
+
+%.$O: %.go
+	$(GC) $*.go
+
+%.$O: %.c
+	$(CC) $*.c
+
+%.$O: %.s
+	$(AS) $*.s
+
+O1=\
+	io.$O\
+	bytebuffer.$O\
+
+io.a: a1
+
+a1:	$(O1)\
+	$(AR) grc io.a io.$O bytebuffer.$O
+	rm -f $(O1)
+
+newpkg: clean
+	$(AR) grc io.a
+
+$(O1): newpkg
+
+nuke: clean
+	rm -f $(GOROOT)/pkg/io.a
+
+packages: io.a
+
+install: packages
+	cp io.a $(GOROOT)/pkg/io.a

src/lib/io.go から src/lib/io/io.go へのファイル移動

ファイルの内容自体は変更されていませんが、パスが変更されました。

コアとなるコードの解説

ByteBufferの実装

ByteBufferは、Go言語におけるバイトバッファの初期的な実装です。io.Readerio.Writerインターフェースを実装しており、バイトデータの読み書きをメモリ上で行うことができます。

  • 動的な容量拡張: Writeメソッドでは、書き込むデータによってバッファの容量が不足した場合、現在の容量の2倍に拡張されます。これは、多くの動的配列やバッファ実装で見られる一般的な戦略で、頻繁な再アロケーションを避けるためのものです。
  • bytecopyの利用: 内部的なデータコピーには、カスタムのbytecopy関数が使用されています。これは、Go言語の初期段階でcopy組み込み関数がまだ最適化されていなかったか、あるいは存在しなかった可能性を示唆しています。
  • offlenによる管理: off(オフセット)は読み込みの開始位置を、len(長さ)は書き込まれたデータの論理的な終端を示します。これにより、バッファの読み書き位置を独立して管理し、効率的なデータの消費と追加を可能にしています。Resetメソッドは、これらのポインタを初期状態に戻し、バッファを再利用可能にします。
  • Data()メソッド: Data()メソッドは、バッファ内の有効なデータ部分をスライスとして返します。これにより、バッファの内容を直接操作したり、他の関数に渡したりすることが容易になります。

このByteBufferは「rudimentary」(初歩的、未発達)とコミットメッセージにある通り、現在のGo標準ライブラリのbytes.Bufferに比べると機能は限定的ですが、Go言語におけるバイトバッファリングの基礎を築いた重要なコンポーネントです。特に、エラーハンドリングが*os.Error型を使用している点や、export typeexport funcといった初期のGoの構文が見られる点も特徴的です。

ビルドシステムの変更の意義

Makefileの変更は、Go言語のビルドシステムが進化していく過程を示しています。

  • パッケージ構造の明確化: io.gosrc/lib/io/io.goに移動し、src/lib/io/Makefileを新設したことで、ioが独立したパッケージとして明確に定義されました。これにより、Goのパッケージ管理の基礎が確立され始めました。
  • 依存関係の管理: Makefile内でio.dirinstallのような依存関係が明示的に定義されることで、ビルドツールがパッケージ間の依存関係を解決し、正しい順序でビルドできるようになりました。これは、大規模なプロジェクトにおけるビルドの信頼性と再現性を高める上で不可欠です。
  • テストとカバレッジの統合: 新しいio/Makefileにはtestcoverageといったターゲットが含まれており、Go言語の初期段階からテストとコードカバレッジが開発プロセスに組み込まれていたことがわかります。これは、Go言語の品質と堅牢性を重視する姿勢を示しています。

これらの変更は、Go言語が単なる実験的な言語から、実用的なシステムプログラミング言語へと成長していくための、重要なインフラストラクチャの整備であったと言えます。

関連リンク

参考にした情報源リンク

  • Go言語の歴史に関する情報 (例: Wikipedia, Go公式ブログの初期記事など)
  • Go言語のMakefileに関する情報 (初期のビルドシステムについて言及している可能性のある記事やドキュメント)
    • Go言語のソースコードリポジトリの歴史的なコミットログやドキュメント
  • Go言語のioパッケージとbytes.Bufferに関する現在のドキュメント
  • Go言語の初期の構文やキーワードに関する情報 (例: exportキーワードなど)
    • Go言語の古いバージョンの言語仕様やチュートリアル