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

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

このコミットは、Go言語の標準ライブラリosパッケージにおけるファイル読み書きの最大サイズに関する変更です。特に、Darwin (macOS) および FreeBSD 環境におけるreadおよびwriteシステムコールの制限に対応するため、一度に読み書きできる最大バイト数を2GB-1から1GBに削減しています。これにより、後続の読み書きにおけるアライメントの問題を回避し、安定性を向上させることを目的としています。

コミット

commit 3879f0abcd94598723d6c3024e5006b52b736b7b
Author: Russ Cox <rsc@golang.org>
Date:   Fri May 2 12:12:40 2014 -0400

    os: cut limited read to 1 GB

    If systems actually read that much, using 2GB-1 will
    result in misaligned subsequent reads. Use 1GB instead,
    which will certainly keep reads aligned and which is
    plenty large enough.

    Update #7812.

    LGTM=bradfitz
    R=bradfitz
    CC=golang-codereviews
    https://golang.org/cl/94070044

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

https://github.com/golang/go/commit/3879f0abcd94598723d6c3024e5006b52b736b7b

元コミット内容

このコミットは、osパッケージにおけるファイル読み書きの最大サイズ制限を2GB-1バイトから1GBに変更するものです。これは、特にDarwinおよびFreeBSDシステムにおいて、一度に大量のデータを読み書きする際に発生する可能性のあるアライメントの問題を解決するための対応です。

変更の背景

この変更の背景には、Go言語のIssue #7812が存在します。このIssueは、「os: cap reads and writes to 2GB on Darwin and FreeBSD」と題されており、Darwin (macOS) および FreeBSD オペレーティングシステムにおいて、たとえ64ビットシステムであっても、readおよびwriteシステムコールが一度に2GBを超えるデータバッファを処理できないという根本的な制限があることを指摘しています。

Goのランタイムは、これらのシステムコールを内部的に利用してファイルの読み書きやネットワーク通信を行いますが、このOSレベルの制限を考慮せずに2GB近いサイズの読み書きを試みると、予期せぬエラーやパフォーマンスの問題が発生する可能性がありました。特に、2GB-1バイトというサイズは、システムによっては内部的なメモリ管理やDMA (Direct Memory Access) のアライメント要件と衝突し、後続の読み書き操作が正しく行われない「misaligned subsequent reads」を引き起こすリスクがありました。

この問題を回避し、GoプログラムがこれらのOS上で安定して動作するようにするため、一度に処理する最大サイズをより安全な値に制限する必要がありました。

前提知識の解説

  • システムコール (System Call): オペレーティングシステムが提供するサービスをプログラムが利用するためのインターフェースです。ファイルI/O (read, write) やネットワーク通信などは、システムコールを通じて行われます。
  • アライメント (Alignment): メモリやディスク上のデータが、特定のバイト境界に配置されることを指します。多くのハードウェアアーキテクチャでは、データが特定のアライメント要件を満たしている場合に、より効率的にアクセスできます。特に、DMA転送などでは、データがページ境界やキャッシュライン境界にアライメントされていることが重要になる場合があります。アライメントが崩れると、パフォーマンスの低下や、最悪の場合データ破損やクラッシュを引き起こす可能性があります。
  • read / write システムコールの制限: 一部のOSでは、readwriteといったI/Oシステムコールが一度に処理できるデータ量に上限を設けています。これは、カーネル内部のバッファ管理、DMAコントローラの制限、またはレガシーな設計に起因することがあります。DarwinやFreeBSDにおける2GBの制限は、このようなOS固有の制約の一例です。
  • 2<<30 - 11 << 30:
    • 2<<302 * (2^30)、つまり 2^31 を意味し、これは2GBです。2<<30 - 1 は2GBから1バイトを引いた値、つまり 2,147,483,647 バイトです。
    • 1 << 302^30 を意味し、これは1GBです。つまり 1,073,741,824 バイトです。 ビットシフト演算子 (<<) は、数値を2のべき乗で乗算する効率的な方法です。

技術的詳細

Goのosパッケージは、ファイルやネットワークソケットからの読み書きを抽象化しています。内部的には、これらの操作はOSの提供するreadwriteシステムコールに依存しています。

このコミット以前は、DarwinおよびFreeBSD環境において、maxRWという定数が2<<30 - 1(約2GB)に設定されていました。これは、これらのOSが一度に処理できる最大サイズを考慮したものでしたが、コミットメッセージにあるように「If systems actually read that much, using 2GB-1 will result in misaligned subsequent reads.」という問題が指摘されました。

この「misaligned subsequent reads」とは、以下のような状況を指します。

  1. Goランタイムが、例えば2GB-1バイトのデータを読み取るシステムコールを発行します。
  2. OSは、この要求を処理し、データをアプリケーションのバッファに書き込みます。
  3. もし、OSの内部的なI/O処理やハードウェアのDMA転送が、特定のページ境界やアライメントを要求する場合、2GB-1バイトというサイズは、次の読み取り操作が開始されるアドレスが、これらのアライメント要件を満たさない可能性がありました。
  4. 結果として、次の読み取り操作が非効率になったり、最悪の場合、OSやハードウェアがエラーを返したり、データが破損したりする可能性がありました。

この問題を解決するため、コミットではmaxRWの値を1 << 30(1GB)に削減しました。1GBというサイズは、多くのシステムにおけるメモリページサイズ(通常4KBや64KB)やキャッシュラインサイズ(通常64バイト)の倍数であり、また2のべき乗であるため、アライメントの問題が発生しにくい安全な値です。

コミットメッセージにある「which will certainly keep reads aligned」という記述は、この1GBというサイズが、OSやハードウェアの内部的なアライメント要件を満たしやすく、後続の読み取り操作が常に適切な境界から開始されることを保証するという意図を示しています。また、「plenty large enough」という記述は、1GBというサイズでもほとんどのユースケースにおいて十分なパフォーマンスを提供できるという判断を示唆しています。

この変更は、osパッケージのfile_unix.goファイルに限定されており、特にUnix系のOS(DarwinとFreeBSD)に特化した修正であることがわかります。

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

変更はsrc/pkg/os/file_unix.goファイル内のmaxRW定数の定義にあります。

--- a/src/pkg/os/file_unix.go
+++ b/src/pkg/os/file_unix.go
@@ -174,9 +174,11 @@ func (f *File) readdir(n int) (fi []FileInfo, err error) {

 // Darwin and FreeBSD can't read or write 2GB+ at a time,
 // even on 64-bit systems. See golang.org/issue/7812.
+// Use 1GB instead of, say, 2GB-1, to keep subsequent
+// reads aligned.
 const (
  	needsMaxRW = runtime.GOOS == "darwin" || runtime.GOOS == "freebsd"
-	maxRW      = 2<<30 - 1
+	maxRW      = 1 << 30
 )

 // read reads up to len(b) bytes from the File.

コアとなるコードの解説

  • needsMaxRW = runtime.GOOS == "darwin" || runtime.GOOS == "freebsd": この行は、maxRW定数による制限が必要なOSを定義しています。runtime.GOOSはGoプログラムが実行されているオペレーティングシステムを識別する定数で、この場合はDarwin (macOS) または FreeBSD の場合にtrueとなります。これにより、この制限が特定のOSにのみ適用されることが保証されます。
  • - maxRW = 2<<30 - 1: 変更前のmaxRWの値です。これは約2GBから1バイトを引いた値であり、以前はこのサイズが最大読み書きサイズとして設定されていました。
  • + maxRW = 1 << 30: 変更後のmaxRWの値です。これは正確に1GBを意味します。この値に設定することで、前述のアライメントの問題を回避し、より安定したI/O操作を可能にします。
  • 追加されたコメント: // Use 1GB instead of, say, 2GB-1, to keep subsequent // reads aligned. このコメントは、なぜ2GB-1ではなく1GBが選ばれたのか、その理由を明確に説明しています。後続の読み取り操作のアライメントを維持するため、という点が重要です。

このmaxRW定数は、File構造体のreadメソッドやwriteメソッド(このdiffには含まれていませんが、関連するコードで利用されます)内で、一度にOSに要求する読み書きの最大サイズを制限するために使用されます。これにより、OSのシステムコールが処理できる範囲内で、かつアライメントの問題を回避できる安全なサイズでI/O操作が行われるようになります。

関連リンク

参考にした情報源リンク