第17章: 標準ライブラリ - 深層解説

はじめに

Goの標準ライブラリは「バッテリー同梱」の思想に基づいて設計されており、ファイル操作、ネットワーク通信、データ処理など、実用的なプログラムに必要な機能がほぼすべて含まれています。この章では、標準ライブラリの内部実装マシンレベルでの動作を徹底的に解説します。

🔑 この章で学ぶこと:

  • io.Reader/Writerインターフェースの設計哲学とメモリ効率
  • fmtパッケージのリフレクション活用とパフォーマンス特性
  • strings/bytesパッケージの最適化技術
  • encoding/jsonの内部実装とメモリアロケーション戦略
  • io パッケージ - 抽象化の真髄

    io.Readerとio.Writerの設計哲学

    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    
    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    

    🔑 なぜこの設計が優れているのか?

  • バッファの所有権が呼び出し側にある
  • ゼロコピーが可能
  • インターフェース分離の原則に従っている
  • メモリレベルでの動作

    ┌─────────────────────────────────────────────────────────┐
    │ io.Readerの動作フロー(メモリレベル)                      │
    ├─────────────────────────────────────────────────────────┤
    │                                                         │
    │  1. 呼び出し側がバッファを確保                             │
    │     buf := make([]byte, 4096)                          │
    │     ↓                                                  │
    │     [Stack]                                            │
    │     buf: {ptr: 0x7fff1234, len: 4096, cap: 4096}      │
    │                                                        │
    │  2. Readメソッド呼び出し                                  │
    │     n, err := reader.Read(buf)                        │
    │     ↓                                                  │
    │     [Interface Value]                                  │
    │     {type: *os.File, data: 0x600001234}              │
    │     ↓                                                  │
    │     [Dynamic Dispatch]                                 │
    │     type assertionなしで直接メソッド呼び出し                │
    │                                                        │
    │  3. 実装側(os.File)がシステムコールを実行                │
    │     syscall.Read(fd, buf, len)                        │
    │     ↓                                                  │
    │     [Kernel Space]                                     │
    │     カーネルバッファ → ユーザーバッファへコピー              │
    │                                                        │
    │  4. 読み込んだバイト数を返す                              │
    │     n = 実際に読み込んだバイト数                          │
    │     buf[0:n] にデータが格納されている                     │
    └─────────────────────────────────────────────────────────┘
    

    実装例と内部動作の詳細

    package main
    
    import (
        "io"
        "os"
        "strings"
    )
    
    func main() {
        // strings.Readerの内部構造
        reader := strings.NewReader("Hello, Go!")
        // 内部的には以下のような構造体:
        // type Reader struct {
        //     s        string  // 元の文字列(不変)
        //     i        int64   // 現在の読み取り位置
        //     prevRune int     // UnreadRune用の前回のrune位置
        // }
    
        // バッファを確保(スタック上)
        buf := make([]byte, 5)
        // buf: {ptr: 0x..., len: 5, cap: 5}
    
        n, err := reader.Read(buf)
        // 内部動作:
        // 1. i(現在位置)をチェック: i = 0
        // 2. 残りバイト数を計算: len(s) - i = 10
        // 3. 読み取るバイト数を決定: min(len(buf), 残りバイト) = 5
        // 4. copy(buf, s[i:i+5]) を実行
        //    - メモリコピー(CPU命令: MOVSB/MOVSW/MOVSQ)
        // 5. i += 5 (位置を更新)
        // 6. return 5, nil
    
        if err != nil {
            panic(err)
        }
        println(string(buf[:n])) // "Hello"
    }
    

    💡 CPUレベルでのコピー処理:

    # x86-64 アセンブリレベルでの copy 操作
    # copy(dst []byte, src []byte) の内部
    
    MOV    RCX, length          # コピーするバイト数をRCXに
    MOV    RSI, src_ptr         # ソースアドレスをRSIに
    MOV    RDI, dst_ptr         # デスティネーションアドレスをRDIに
    
    # 最適化されたコピーループ
    CMP    RCX, 32              # 32バイト以上か?
    JB     small_copy           # 小さい場合は単純コピー
    
    # SIMD命令を使った高速コピー(32バイト単位)
    aligned_copy:
        VMOVDQU YMM0, [RSI]     # 32バイトをYMM0レジスタに読み込み
        VMOVDQU [RDI], YMM0     # YMM0から32バイトを書き込み
        ADD     RSI, 32
        ADD     RDI, 32
        SUB     RCX, 32
        CMP     RCX, 32
        JAE     aligned_copy
    
    small_copy:
        REP MOVSB               # 残りバイトを1バイトずつコピー
    

    io.Copyの最適化技術

    func Copy(dst Writer, src Reader) (written int64, err error)
    

    🔑 io.Copyが高速な理由:

  • WriterToインターフェースのチェック
  • ReaderFromインターフェースのチェック
  • 内部バッファの再利用
  • 内部実装の詳細

    // src/io/io.go の実装(簡略化)
    func Copy(dst Writer, src Reader) (written int64, err error) {
        // 1. WriterTo インターフェースをチェック
        if wt, ok := src.(WriterTo); ok {
            return wt.WriteTo(dst)
            // → ゼロコピーまたは最適化されたコピーが可能
        }
    
        // 2. ReaderFrom インターフェースをチェック
        if rf, ok := dst.(ReaderFrom); ok {
            return rf.ReadFrom(src)
            // → 例: *os.File.ReadFrom は sendfile(2) を使用
        }
    
        // 3. 通常のバッファを使ったコピー
        buf := make([]byte, 32*1024) // 32KB バッファ
        for {
            nr, er := src.Read(buf)
            if nr > 0 {
                nw, ew := dst.Write(buf[0:nr])
                written += int64(nw)
                if ew != nil {
                    err = ew
                    break
                }
            }
            if er != nil {
                if er != EOF {
                    err = er
                }
                break
            }
        }
        return written, err
    }
    

    sendfile(2) システムコールによる最適化

    ┌────────────────────────────────────────────────────────┐
    │ 通常のコピー vs sendfile(2)                              │
    ├────────────────────────────────────────────────────────┤
    │                                                        │
    │ 通常のコピー(4回のコンテキストスイッチ):                │
    │                                                        │
    │  [User Space]                                         │
    │     read(fd_in, buf, size)                           │
    │  ↓ [1] システムコール                                  │
    │  [Kernel Space]                                       │
    │     カーネルバッファ → ユーザーバッファ                   │
    │  ↑ [2] return                                         │
    │  [User Space]                                         │
    │     write(fd_out, buf, size)                         │
    │  ↓ [3] システムコール                                  │
    │  [Kernel Space]                                       │
    │     ユーザーバッファ → カーネルバッファ                   │
    │  ↑ [4] return                                         │
    │                                                        │
    │ sendfile(2)(2回のコンテキストスイッチ):                │
    │                                                        │
    │  [User Space]                                         │
    │     sendfile(fd_out, fd_in, offset, count)          │
    │  ↓ [1] システムコール                                  │
    │  [Kernel Space]                                       │
    │     カーネル内でファイル→ファイルへ直接転送               │
    │     (DMAを使用してCPUを介さない転送も可能)              │
    │  ↑ [2] return                                         │
    │                                                        │
    │ パフォーマンス差: sendfile は通常のコピーより            │
    │ 2-3倍高速(CPUコピー削減、コンテキストスイッチ削減)      │
    └────────────────────────────────────────────────────────┘
    

    io.ReadAllの実装とメモリ戦略

    func ReadAll(r Reader) ([]byte, error)
    

    内部実装の詳細

    // src/io/io.go
    const maxInt = int(^uint(0) >> 1)
    
    func ReadAll(r Reader) ([]byte, error) {
        b := make([]byte, 0, 512) // 初期容量512バイト
        for {
            // 容量が足りない場合は拡張
            if len(b) == cap(b) {
                // 拡張戦略: 既存サイズの2倍 + 最小512バイト
                b = append(b, 0)[:len(b)]
            }
            n, err := r.Read(b[len(b):cap(b)])
            b = b[:len(b)+n]
            if err != nil {
                if err == EOF {
                    err = nil
                }
                return b, err
            }
        }
    }
    

    💡 スライス拡張のメモリアロケーション:

    ┌───────────────────────────────────────────────────────┐
    │ ReadAllのメモリ拡張パターン                              │
    ├───────────────────────────────────────────────────────┤
    │                                                       │
    │ 初期: len=0, cap=512                                  │
    │ [                512バイト                   ]        │
    │  ↓ 512バイト読み込み                                  │
    │                                                       │
    │ len=512, cap=512 (容量満杯)                           │
    │ [################512バイト################]           │
    │  ↓ append による自動拡張(2倍 = 1024バイト)            │
    │                                                       │
    │ 新しい配列を確保してコピー:                             │
    │ [################512バイト################|     512   ]│
    │  旧配列                                    新領域      │
    │  ↓ 512バイト読み込み                                  │
    │                                                       │
    │ len=1024, cap=1024                                   │
    │ [################1024バイト###########################]│
    │  ↓ 再度拡張(2048バイト)                             │
    │                                                       │
    │ メモリコスト:                                          │
    │ - 最悪ケース: O(n log n) のコピーコスト                │
    │ - 平均ケース: O(n) の償却コスト                        │
    │ - メモリオーバーヘッド: 最大2倍のメモリ使用              │
    └───────────────────────────────────────────────────────┘
    

    ⚠️ ReadAllの注意点:

    // 危険な例: 無制限のデータを読み込む
    resp, _ := http.Get("http://example.com/huge-file")
    data, err := io.ReadAll(resp.Body) // メモリ不足の可能性!
    
    // 安全な例: サイズ制限付き Reader
    limitedReader := io.LimitReader(resp.Body, 10*1024*1024) // 10MB制限
    data, err := io.ReadAll(limitedReader)
    

    fmt パッケージ - リフレクションとフォーマット

    fmtパッケージの内部構造

    func Printf(format string, a ...interface{}) (n int, err error)
    

    🔑 fmtパッケージの実装技術:

  • リフレクションによる型情報の取得
  • 型スイッチによる最適化
  • バッファプールによるメモリ効率化
  • リフレクションの動作原理

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func demonstrateReflection() {
        var x int = 42
    
        // fmt.Printf("%v", x) の内部動作
        // 1. interface{} に変換
        var i interface{} = x
    
        // 2. interface value の構造:
        // type interfaceValue struct {
        //     typ  *_type     // 型情報へのポインタ
        //     data unsafe.Pointer  // 実データへのポインタ
        // }
    
        // 3. リフレクションで型情報を取得
        v := reflect.ValueOf(i)
        t := v.Type()
    
        fmt.Printf("Type: %v, Kind: %v, Value: %v\n",
            t.Name(), t.Kind(), v.Int())
        // 出力: Type: int, Kind: int, Value: 42
    }
    

    メモリレベルでのinterface{}表現

    ┌──────────────────────────────────────────────────────┐
    │ interface{} のメモリレイアウト                          │
    ├──────────────────────────────────────────────────────┤
    │                                                      │
    │ var i interface{} = 42                              │
    │                                                      │
    │ [メモリダンプ]                                        │
    │ i: {                                                │
    │     _type: 0x10a4f20  ───┐                         │
    │     data:  0x10c3e80  ───┼──┐                      │
    │ }                         │  │                      │
    │                           │  │                      │
    │ 0x10a4f20 (_type):       │  │                      │
    │ ┌─────────────────────┐ │  │                      │
    │ │ size:    8          │◄┘  │                      │
    │ │ ptrdata: 0          │    │                      │
    │ │ hash:    0xf75371fa │    │                      │
    │ │ tflag:   7          │    │                      │
    │ │ align:   8          │    │                      │
    │ │ kind:    2 (int)    │    │                      │
    │ │ str:     0x10a5f28──────"int"                   │
    │ └─────────────────────┘    │                      │
    │                             │                      │
    │ 0x10c3e80 (data):          │                      │
    │ ┌─────────────────────┐   │                      │
    │ │ 0x2a (42 in hex)    │◄──┘                      │
    │ └─────────────────────┘                           │
    │                                                      │
    │ 型情報のサイズ: 48バイト                             │
    │ データのサイズ: 8バイト(int)                        │
    │ interface{}のサイズ: 16バイト(ポインタ2つ)           │
    └──────────────────────────────────────────────────────┘
    

    fmt.Printfの内部実装

    // src/fmt/print.go の簡略化版
    func Printf(format string, a ...interface{}) (n int, err error) {
        // 1. バッファプールから再利用可能なバッファを取得
        p := newPrinter()
    
        // 2. フォーマット文字列を解析してプリント
        p.doPrintf(format, a)
    
        // 3. 標準出力に書き込み
        n, err = os.Stdout.Write(p.buf)
    
        // 4. バッファをプールに戻す(重要!)
        p.free()
    
        return
    }
    
    type pp struct {
        buf    []byte  // 出力バッファ
        arg    interface{}  // 現在処理中の引数
        value  reflect.Value  // リフレクション値
        fmt    fmt_  // フォーマット情報
        // ... その他のフィールド
    }
    
    var ppFree = sync.Pool{
        New: func() interface{} { return new(pp) },
    }
    
    func newPrinter() *pp {
        p := ppFree.Get().(*pp)
        p.panicking = false
        p.erroring = false
        p.wrapErrs = false
        p.fmt.init(&p.buf)
        return p
    }
    
    func (p *pp) free() {
        // バッファが大きすぎる場合は捨てる
        if cap(p.buf) > 64<<10 {
            return
        }
    
        p.buf = p.buf[:0]
        p.arg = nil
        p.value = reflect.Value{}
        ppFree.Put(p)
    }
    

    💡 sync.Poolによる最適化:

    ┌─────────────────────────────────────────────────────┐
    │ sync.Pool の動作メカニズム                            │
    ├─────────────────────────────────────────────────────┤
    │                                                     │
    │ [Goroutine 1]    [Goroutine 2]    [Goroutine 3]   │
    │      ↓                ↓                ↓           │
    │   Get()           Get()            Get()          │
    │      ↓                ↓                ↓           │
    │ [Per-P Pool]    [Per-P Pool]    [Per-P Pool]      │
    │  P0: [obj1]      P1: [obj2]      P2: [obj3]       │
    │      ↓                ↓                ↓           │
    │  使用中           使用中             使用中         │
    │      ↓                ↓                ↓           │
    │   Put()           Put()            Put()          │
    │      ↓                ↓                ↓           │
    │ [Per-P Pool]    [Per-P Pool]    [Per-P Pool]      │
    │  P0: [obj1]      P1: [obj2]      P2: [obj3]       │
    │                                                     │
    │ GCが発生すると...                                    │
    │      ↓                ↓                ↓           │
    │ [Per-P Pool]    [Per-P Pool]    [Per-P Pool]      │
    │  P0: []          P1: []          P2: []           │
    │  (すべてクリア)                                     │
    │                                                     │
    │ メリット:                                            │
    │ - ロックフリー(各PごとにPool)                       │
    │ - メモリプレッシャーが高い時は自動解放                 │
    │ - アロケーション削減(ベンチマーク: 30-50%削減)       │
    └─────────────────────────────────────────────────────┘
    

    フォーマット指定子の処理

    // %v, %+v, %#v の違い
    type Person struct {
        Name string
        Age  int
    }
    
    p := Person{"Alice", 30}
    
    fmt.Printf("%v\n", p)   // {Alice 30}
    fmt.Printf("%+v\n", p)  // {Name:Alice Age:30}
    fmt.Printf("%#v\n", p)  // main.Person{Name:"Alice", Age:30}
    

    内部処理フロー

    ┌────────────────────────────────────────────────────┐
    │ fmt.Printf("%+v", structValue) の処理フロー          │
    ├────────────────────────────────────────────────────┤
    │                                                    │
    │ 1. フォーマット文字列の解析                          │
    │    "%+v" → {verb: 'v', plus: true}                │
    │                                                    │
    │ 2. 引数の型を判定                                   │
    │    structValue → reflect.Kind() == reflect.Struct │
    │                                                    │
    │ 3. 型に応じた処理を選択                              │
    │    switch kind {                                  │
    │    case reflect.Struct:                           │
    │        → printStructValue()                       │
    │    case reflect.Int:                              │
    │        → printInt()                               │
    │    // ...                                         │
    │    }                                              │
    │                                                    │
    │ 4. 構造体のフィールドを反復処理                       │
    │    for i := 0; i < numField; i++ {               │
    │        field := t.Field(i)                       │
    │        value := v.Field(i)                       │
    │        if plus {                                  │
    │            buf.WriteString(field.Name)           │
    │            buf.WriteByte(':')                    │
    │        }                                          │
    │        printValue(value)                         │
    │    }                                              │
    │                                                    │
    │ 5. バッファの内容を出力                              │
    │    os.Stdout.Write(buf)                          │
    │                                                    │
    │ パフォーマンス特性:                                   │
    │ - リフレクションコスト: 通常の関数呼び出しの10-100倍   │
    │ - バッファリングによる緩和: Write回数削減              │
    │ - sync.Poolによるアロケーション削減                   │
    └────────────────────────────────────────────────────┘
    

    strings パッケージ - 最適化の宝庫

    strings.Builderの内部実装

    type Builder struct {
        addr *Builder  // アドレス比較用(コピー検出)
        buf  []byte    // バッファ
    }
    

    🔑 なぜstrings.Builderが高速なのか?

  • バッファの成長戦略が最適化されている
  • 不要なコピーを回避
  • unsafe.Stringを使ったゼロコピー変換
  • メモリアロケーション戦略

    func (b *Builder) grow(n int) {
        buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
        copy(buf, b.buf)
        b.buf = buf
    }
    

    💡 成長戦略の詳細:

    ┌──────────────────────────────────────────────────────┐
    │ strings.Builder のバッファ成長パターン                  │
    ├──────────────────────────────────────────────────────┤
    │                                                      │
    │ 初期状態: cap=0                                       │
    │ []                                                   │
    │                                                      │
    │ WriteString("Hello") → 5バイト必要                   │
    │ grow(5) → 新容量 = 2*0 + 5 = 5                       │
    │ [Hello]                                              │
    │ cap=5, len=5                                         │
    │                                                      │
    │ WriteString(" World") → 6バイト必要                  │
    │ grow(6) → 新容量 = 2*5 + 6 = 16                      │
    │ [Hello World     ]                                   │
    │ cap=16, len=11                                       │
    │                                                      │
    │ WriteString("!!!!") → 4バイト必要(容量内)            │
    │ [Hello World!!!!]                                    │
    │ cap=16, len=15                                       │
    │                                                      │
    │ WriteString("XX") → 2バイト必要                       │
    │ grow(2) → 新容量 = 2*16 + 2 = 34                     │
    │ [Hello World!!!!XX                  ]                │
    │ cap=34, len=17                                       │
    │                                                      │
    │ 成長係数: 2倍 + 必要量                                 │
    │ → append の 2倍成長より少し積極的                      │
    │ → 頻繁な再アロケーションを防ぐ                          │
    └──────────────────────────────────────────────────────┘
    

    unsafe.Stringによるゼロコピー変換

    // src/strings/builder.go
    func (b *Builder) String() string {
        return unsafe.String(unsafe.SliceData(b.buf), len(b.buf))
    }
    
    // 古い実装(Go 1.20以前):
    // return *(*string)(unsafe.Pointer(&b.buf))
    

    💡 unsafe.Stringの仕組み:

    ┌────────────────────────────────────────────────────┐
    │ []byte から string へのゼロコピー変換                 │
    ├────────────────────────────────────────────────────┤
    │                                                    │
    │ 通常の変換: string(b.buf)                           │
    │                                                    │
    │ [Heap]                                             │
    │  b.buf: [H][e][l][l][o]  ← 元のバイトスライス        │
    │           ↓                                        │
    │         コピー(メモリアロケーション発生)              │
    │           ↓                                        │
    │  result: [H][e][l][l][o]  ← 新しい文字列             │
    │                                                    │
    │ コスト: O(n) のメモリコピー + アロケーション            │
    │                                                    │
    │─────────────────────────────────────────────────────│
    │                                                    │
    │ unsafe.String を使った変換:                         │
    │                                                    │
    │ [Heap]                                             │
    │  b.buf: [H][e][l][l][o]  ← 元のバイトスライス        │
    │           ↑                                        │
    │           │ ポインタ共有(コピーなし)                │
    │           ↑                                        │
    │  result: string{ptr: &buf[0], len: 5}             │
    │                                                    │
    │ コスト: O(1) のポインタ操作のみ                       │
    │                                                    │
    │ ⚠️ 注意: bufが変更されるとstringも変わる              │
    │    → Builder は String() 後の書き込みを禁止          │
    └────────────────────────────────────────────────────┘
    

    文字列連結のパフォーマンス比較

    package main
    
    import (
        "strings"
        "testing"
    )
    
    // ベンチマーク1: 単純な + 演算子
    func BenchmarkStringConcat(b *testing.B) {
        b.ReportAllocs()
        for i := 0; i < b.N; i++ {
            s := ""
            for j := 0; j < 100; j++ {
                s += "x"  // 毎回新しい文字列を作成
            }
        }
    }
    
    // ベンチマーク2: strings.Builder
    func BenchmarkStringBuilder(b *testing.B) {
        b.ReportAllocs()
        for i := 0; i < b.N; i++ {
            var builder strings.Builder
            builder.Grow(100)  // 事前に容量確保
            for j := 0; j < 100; j++ {
                builder.WriteString("x")
            }
            _ = builder.String()
        }
    }
    
    // ベンチマーク3: bytes.Buffer
    func BenchmarkBytesBuffer(b *testing.B) {
        b.ReportAllocs()
        for i := 0; i < b.N; i++ {
            var buf bytes.Buffer
            buf.Grow(100)
            for j := 0; j < 100; j++ {
                buf.WriteString("x")
            }
            _ = buf.String()
        }
    }
    
    // ベンチマーク4: []byte + string() 変換
    func BenchmarkByteSlice(b *testing.B) {
        b.ReportAllocs()
        for i := 0; i < b.N; i++ {
            buf := make([]byte, 0, 100)
            for j := 0; j < 100; j++ {
                buf = append(buf, 'x')
            }
            _ = string(buf)  // コピーが発生
        }
    }
    

    ベンチマーク結果の分析

    ┌────────────────────────────────────────────────────────────┐
    │ ベンチマーク結果(100回の文字列連結)                         │
    ├────────────────────────────────────────────────────────────┤
    │                                                            │
    │ 方法                 時間       メモリ     アロケーション回数 │
    │ ─────────────────────────────────────────────────────────  │
    │ + 演算子          18,450 ns   24,576 B      100 allocs    │
    │ strings.Builder      340 ns      224 B        2 allocs    │
    │ bytes.Buffer         410 ns      336 B        3 allocs    │
    │ []byte + string()    380 ns      224 B        2 allocs    │
    │                                                            │
    │ 詳細分析:                                                   │
    │                                                            │
    │ 1. + 演算子の問題:                                          │
    │    - 各連結で新しい文字列を作成                              │
    │    - メモリコピーが O(n²) になる                            │
    │    - 1+2+3+...+100 = 5,050バイトのコピー                   │
    │                                                            │
    │ 2. strings.Builderの優位性:                                │
    │    - 事前容量確保で再アロケーション回避                       │
    │    - unsafe.Stringでゼロコピー変換                          │
    │    - 最小限のメモリオーバーヘッド                             │
    │                                                            │
    │ 3. bytes.Bufferとの差:                                     │
    │    - Bufferはio.Readerインターフェースを実装                │
    │    - 若干のメモリオーバーヘッドがある                         │
    │    - String()でコピーが発生                                 │
    │                                                            │
    │ 結論: 単純な文字列構築にはstrings.Builderが最適             │
    └────────────────────────────────────────────────────────────┘
    

    strings パッケージの最適化関数

    // strings.Contains の実装
    func Contains(s, substr string) bool {
        return Index(s, substr) >= 0
    }
    
    // strings.Index の内部実装(Rabin-Karpアルゴリズム使用)
    func Index(s, substr string) int {
        n := len(substr)
        switch {
        case n == 0:
            return 0
        case n == 1:
            return IndexByte(s, substr[0])  // 高速化
        case n == len(s):
            if substr == s {
                return 0
            }
            return -1
        case n > len(s):
            return -1
        }
    
        // Rabin-Karpハッシュ検索
        // または Boyer-Moore-Horspool アルゴリズム
        // 文字列長に応じて最適なアルゴリズムを選択
    }
    

    💡 文字列検索アルゴリズムの選択:

    ┌──────────────────────────────────────────────────────┐
    │ strings.Index のアルゴリズム選択戦略                    │
    ├──────────────────────────────────────────────────────┤
    │                                                      │
    │ 検索文字列の長さ    アルゴリズム       時間計算量       │
    │ ────────────────────────────────────────────────────  │
    │ n == 0             即座に0を返す       O(1)          │
    │ n == 1             IndexByte           O(n)          │
    │                    (SIMD最適化)                      │
    │ n <= shortStringLen Brute Force       O(nm)         │
    │  (32バイト)        (シンプルで速い)                   │
    │ n > 32             Rabin-Karp         O(n+m)        │
    │                    または BMH                         │
    │                                                      │
    │ IndexByte の SIMD 最適化:                            │
    │                                                      │
    │ # SSE2命令を使った並列比較                            │
    │ MOVD      XMM0, byte_to_find  # 検索バイトをXMM0に    │
    │ PSHUFB    XMM0, XMM0          # 16バイト分複製       │
    │                                                      │
    │ loop:                                                │
    │   MOVDQU  XMM1, [RSI]         # 16バイト読み込み     │
    │   PCMPEQB XMM1, XMM0          # 並列比較             │
    │   PMOVMSKB EAX, XMM1          # 比較結果をビットマスク │
    │   TEST    EAX, EAX            # 一致あり?           │
    │   JNZ     found               # あれば終了           │
    │   ADD     RSI, 16             # 次の16バイトへ       │
    │   JMP     loop                                       │
    │                                                      │
    │ → 1命令で16バイトを同時比較                           │
    │ → 理論上16倍の高速化                                  │
    └──────────────────────────────────────────────────────┘
    

    encoding/json パッケージ - マーシャリングの秘密

    json.Marshalの内部実装

    func Marshal(v interface{}) ([]byte, error)
    

    🔑 json.Marshalの処理フロー:

  • 型情報のキャッシュ(sync.Map使用)
  • リフレクションによる構造解析
  • encodeState バッファへの書き込み
  • エスケープ処理の最適化

内部構造とキャッシング

// src/encoding/json/encode.go
type encodeState struct {
    bytes.Buffer  // 出力バッファ

    scratch      [64]byte  // 小さな値用のスクラッチ領域
    ptrLevel     uint
    ptrSeen      map[interface{}]struct{}  // 循環参照検出
}

var encodeStatePool = sync.Pool{
    New: func() interface{} {
        return &encodeState{
            ptrSeen: make(map[interface{}]struct{}),
        }
    },
}

// 型情報のキャッシュ(グローバル)
var encoderCache sync.Map // map[reflect.Type]encoderFunc

💡 型情報キャッシングの仕組み:

┌─────────────────────────────────────────────────────┐
│ エンコーダキャッシュの動作                             │
├─────────────────────────────────────────────────────┤
│                                                     │
│ 初回エンコード:                                       │
│                                                     │
│ type User struct {                                 │
│     Name string `json:"name"`                      │
│     Age  int    `json:"age"`                       │
│ }                                                   │
│                                                     │
│ json.Marshal(user)                                 │
│   ↓                                                │
│ reflect.TypeOf(user) → reflect.Type                │
│   ↓                                                │
│ encoderCache.Load(type) → nil (初回)                │
│   ↓                                                │
│ typeEncoder(type) で新規作成:                        │
│   - 構造体のフィールドを解析                          │
│   - jsonタグを解析                                   │
│   - encoderFunc を生成                              │
│   ↓                                                │
│ encoderCache.Store(type, encoderFunc)              │
│                                                     │
│ 2回目以降のエンコード:                                │
│                                                     │
│ json.Marshal(user2)                                │
│   ↓                                                │
│ reflect.TypeOf(user2) → reflect.Type (同じ型)       │
│   ↓                                                │
│ encoderCache.Load(type) → encoderFunc (ヒット!)     │
│   ↓                                                │
│ キャッシュされたencoderFuncを直接使用                 │
│                                                     │
│ パフォーマンス改善:                                   │
│ - 初回: ~1000 ns (リフレクション + キャッシュ登録)     │
│ - 2回目以降: ~200 ns (キャッシュヒット)               │
│ → 約5倍の高速化                                      │
└─────────────────────────────────────────────────────┘

構造体エンコーディングの詳細

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email,omitempty"`
    Password string `json:"-"`
    private  string // エクスポートされていない
}

user := User{
    ID:       1,
    Name:     "Alice",
    Email:    "",
    Password: "secret",
    private:  "hidden",
}

data, _ := json.Marshal(user)
// 結果: {"id":1,"name":"Alice"}

フィールド処理の詳細フロー

┌──────────────────────────────────────────────────────────┐
│ 構造体エンコーディングの処理フロー                          │
├──────────────────────────────────────────────────────────┤
│                                                          │
│ 1. 型情報の取得                                           │
│    t := reflect.TypeOf(user)                            │
│    → reflect.Struct                                     │
│                                                          │
│ 2. フィールドの走査(各フィールド):                        │
│                                                          │
│    Field: ID                                            │
│      ✓ Exported (大文字始まり)                           │
│      ✓ Tag: "id"                                        │
│      → エンコード対象                                     │
│      → writeInt(1)                                      │
│                                                          │
│    Field: Name                                          │
│      ✓ Exported                                         │
│      ✓ Tag: "name"                                      │
│      → エンコード対象                                     │
│      → writeString("Alice")                             │
│                                                          │
│    Field: Email                                         │
│      ✓ Exported                                         │
│      ✓ Tag: "email,omitempty"                          │
│      ✗ Value: "" (空文字列)                              │
│      → omitempty により スキップ                          │
│                                                          │
│    Field: Password                                      │
│      ✓ Exported                                         │
│      ✗ Tag: "-" (無視指定)                               │
│      → スキップ                                          │
│                                                          │
│    Field: private                                       │
│      ✗ Not exported (小文字始まり)                        │
│      → スキップ                                          │
│                                                          │
│ 3. 出力バッファの構築                                      │
│    buf: ['{']['"']['i']['d']['"'][':']['1']            │
│         [',']['"']['n']['a']['m']['e']['"'][':']       │
│         ['"']['A']['l']['i']['c']['e']['"']['}']       │
│                                                          │
│ 4. []byte への変換                                        │
│    return buf.Bytes()                                   │
└──────────────────────────────────────────────────────────┘

文字列エスケープの最適化

// src/encoding/json/encode.go
func (e *encodeState) string(s string, escapeHTML bool) {
    e.WriteByte('"')

    start := 0
    for i := 0; i < len(s); {
        if b := s[i]; b < utf8.RuneSelf {
            // ASCII範囲の高速パス
            if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) {
                i++
                continue
            }

            // エスケープが必要
            if start < i {
                e.WriteString(s[start:i])
            }

            e.WriteByte('\\')
            switch b {
            case '\\', '"':
                e.WriteByte(b)
            case '\n':
                e.WriteByte('n')
            case '\r':
                e.WriteByte('r')
            case '\t':
                e.WriteByte('t')
            default:
                // \uXXXX形式
                e.WriteString(`u00`)
                e.WriteByte(hex[b>>4])
                e.WriteByte(hex[b&0xF])
            }

            i++
            start = i
            continue
        }

        // マルチバイト文字(UTF-8)
        c, size := utf8.DecodeRuneInString(s[i:])
        i += size
    }

    if start < len(s) {
        e.WriteString(s[start:])
    }

    e.WriteByte('"')
}

var htmlSafeSet = [256]bool{...}  // ルックアップテーブル
var safeSet = [256]bool{...}

💡 エスケープ処理のパフォーマンス最適化:

┌──────────────────────────────────────────────────────┐
│ 文字列エスケープの最適化技術                           │
├──────────────────────────────────────────────────────┤
│                                                      │
│ 1. ルックアップテーブルによる高速判定                   │
│                                                      │
│    var safeSet = [256]bool{                         │
│        ' ': false, '!': true, '"': false, ...       │
│    }                                                 │
│                                                      │
│    if safeSet[b] {  // O(1) の配列アクセス            │
│        // エスケープ不要                              │
│    }                                                 │
│                                                      │
│    vs. switch文やif-else連鎖: O(log n) または O(n)   │
│                                                      │
│ 2. バッチ書き込み                                     │
│                                                      │
│    エスケープ不要な部分をまとめて書き込み:              │
│                                                      │
│    "Hello, World!"                                  │
│     ↓                                               │
│    start=0, i=0                                     │
│    → H,e,l,l,o,,,W,o,r,l,d,! (すべて安全)           │
│    → WriteString(s[0:13]) (1回の書き込み)            │
│                                                      │
│    vs. 1文字ずつ書き込み: WriteByte 13回             │
│                                                      │
│ 3. ASCII 高速パス                                    │
│                                                      │
│    if b < utf8.RuneSelf { // b < 128                │
│        // ASCII範囲 → 1バイト処理                    │
│    } else {                                          │
│        // マルチバイトUTF-8 → 複雑な処理              │
│    }                                                 │
│                                                      │
│    ASCII文字列の処理: ~100 MB/s                      │
│    マルチバイト文字列: ~50 MB/s                       │
│                                                      │
│ 4. メモリアロケーション削減                            │
│                                                      │
│    encodeState.scratch [64]byte                     │
│    → 小さな値は事前確保済みバッファを使用              │
│    → ヒープアロケーション回避                          │
└──────────────────────────────────────────────────────┘

json.Unmarshalの最適化

func Unmarshal(data []byte, v interface{}) error

デコーダの内部状態

type decodeState struct {
    data       []byte
    off        int // 現在の読み取り位置
    opcode     int // 次の操作コード
    scan       scanner
    errorContext *errorContext
    savedError   error
    useNumber    bool
    disallowUnknownFields bool
}

type scanner struct {
    step func(*scanner, byte) int
    endTop bool

    parseState []int  // パース状態のスタック
    err        error
}

💡 ステートマシンによるパース:

┌─────────────────────────────────────────────────────┐
│ JSON パーサーのステートマシン                          │
├─────────────────────────────────────────────────────┤
│                                                     │
│ 入力: {"name":"Alice","age":30}                    │
│                                                     │
│ State        Byte   Next State    Action          │
│ ──────────────────────────────────────────────────  │
│ stateBegin   '{'    stateBeginObject  push('{'  ) │
│ stateBeginObj '"'   stateBeginString  start key  │
│ ...String    'n'    stateInString     append     │
│ ...String    'a'    stateInString     append     │
│ ...String    'm'    stateInString     append     │
│ ...String    'e'    stateInString     append     │
│ ...String    '"'    stateEndValue     end key    │
│ stateEndVal  ':'    stateBeginValue   expect val │
│ stateBeginVal '"'   stateBeginString  start val  │
│ ...String    'A'    stateInString     append     │
│ ...String    'l'    stateInString     append     │
│ ...String    'i'    stateInString     append     │
│ ...String    'c'    stateInString     append     │
│ ...String    'e'    stateInString     append     │
│ ...String    '"'    stateEndValue     end val    │
│ stateEndVal  ','    stateBeginName    next field │
│ ...                                               │
│                                                     │
│ ステートマシンの利点:                                 │
│ - 1バイトずつ処理 → ストリーミング可能                 │
│ - 状態遷移テーブル → 高速                             │
│ - エラー検出が容易                                    │
│ - メモリ効率的(先読み不要)                           │
└─────────────────────────────────────────────────────┘

カスタムマーシャラーのパフォーマンス

type Timestamp time.Time

// 効率的な実装
func (t Timestamp) MarshalJSON() ([]byte, error) {
    // 事前確保されたバッファを使用
    b := make([]byte, 0, len(time.RFC3339)+2)
    b = append(b, '"')
    b = time.Time(t).AppendFormat(b, time.RFC3339)
    b = append(b, '"')
    return b, nil
}

// 非効率な実装
func (t Timestamp) MarshalJSONSlow() ([]byte, error) {
    s := `"` + time.Time(t).Format(time.RFC3339) + `"`
    return []byte(s), nil
    // 問題:
    // 1. 文字列連結で3回のアロケーション
    // 2. []byte変換で追加のコピー
}

time パッケージ - 時刻表現の内部

time.Timeの内部表現

type Time struct {
    wall uint64  // 壁時計時刻(1885年1月1日からのナノ秒)
    ext  int64   // 拡張情報(モノトニッククロック)
    loc  *Location
}

💡 壁時計時刻とモノトニッククロック:

┌──────────────────────────────────────────────────────┐
│ time.Time の二重時刻表現                               │
├──────────────────────────────────────────────────────┤
│                                                      │
│ wall (uint64):                                      │
│ ┌────────┬──────────────────────────────────────┐   │
│ │1bit flag│   33bit sec   │   30bit nsec       │   │
│ └────────┴──────────────────────────────────────┘   │
│  ↑        ↑                 ↑                       │
│  │        │                 └─ ナノ秒部分(0-999M)   │
│  │        └─ 秒部分(1885年からの経過秒)             │
│  └─ hasMonotonic フラグ                             │
│                                                      │
│ ext (int64):                                        │
│  - hasMonotonic=1: モノトニッククロック値              │
│  - hasMonotonic=0: 拡張秒部分                        │
│                                                      │
│ 二重時刻の用途:                                       │
│                                                      │
│ 1. 壁時計時刻(wall):                               │
│    - 実世界の時刻表現                                 │
│    - NTPによる調整の影響を受ける                       │
│    - Format(), Unix() などで使用                     │
│                                                      │
│ 2. モノトニッククロック(ext):                        │
│    - 単調増加する時刻                                 │
│    - 時刻調整の影響を受けない                          │
│    - Sub(), Since() などで使用                       │
│                                                      │
│ 例: 経過時間測定                                      │
│                                                      │
│ start := time.Now()                                 │
│ // wall=12345, ext=67890 (monotonic)               │
│                                                      │
│ // NTPが1秒戻した!                                  │
│                                                      │
│ end := time.Now()                                   │
│ // wall=12344, ext=67900 (monotonic)               │
│                                                      │
│ elapsed := end.Sub(start)                          │
│ // モノトニッククロックを使用: 67900-67890=10        │
│ // → 正しく10ナノ秒を返す                            │
│ // (wall時刻を使うと負の値になってしまう)            │
└──────────────────────────────────────────────────────┘

タイマーの内部実装

type Timer struct {
    C <-chan Time
    r runtimeTimer
}

type runtimeTimer struct {
    pp       uintptr
    when     int64
    period   int64
    f        func(interface{}, uintptr)
    arg      interface{}
    seq      uintptr
    nextwhen int64
    status   uint32
}

💡 ランタイムタイマーヒープ:

┌──────────────────────────────────────────────────────┐
│ Goランタイムのタイマー管理                              │
├──────────────────────────────────────────────────────┤
│                                                      │
│ [Per-P Timer Heap]                                  │
│                                                      │
│ P0:  Min-Heap                                       │
│      ┌─────┐                                         │
│      │ 100ms│                                        │
│      └─┬───┘                                         │
│       ┌┴──┬──┐                                       │
│    ┌──┴┐ ┌┴──┐                                      │
│    │200ms│300ms│                                     │
│    └───┘ └───┘                                       │
│                                                      │
│ P1:  Min-Heap                                       │
│      ┌─────┐                                         │
│      │ 50ms│                                         │
│      └─┬───┘                                         │
│       ┌┴──┬──┐                                       │
│    ┌──┴┐ ┌┴──┐                                      │
│    │150ms│250ms│                                     │
│    └───┘ └───┘                                       │
│                                                      │
│ タイマー処理の流れ:                                    │
│                                                      │
│ 1. time.NewTimer(d) が呼ばれる                       │
│    ↓                                                │
│ 2. 現在のP(プロセッサ)のヒープに挿入                  │
│    when = now() + d                                 │
│    ↓                                                │
│ 3. sysmon goroutine が定期的にチェック                │
│    (10ms間隔)                                        │
│    ↓                                                │
│ 4. ヒープのトップ(最小値)をチェック                   │
│    if now() >= heap.top.when:                       │
│        タイマーを発火                                 │
│    ↓                                                │
│ 5. コールバック関数を実行                              │
│    f(arg)                                           │
│                                                      │
│ ヒープ操作の計算量:                                    │
│ - 挿入: O(log n)                                     │
│ - 削除: O(log n)                                     │
│ - 最小値取得: O(1)                                    │
│                                                      │
│ Per-P ヒープの利点:                                   │
│ - ロック不要(各Pが独自のヒープを持つ)                 │
│ - キャッシュ効率が良い                                 │
│ - スケーラビリティ向上                                 │
└──────────────────────────────────────────────────────┘

strconv パッケージ - 高速変換の技術

Atoiの内部実装

func Atoi(s string) (int, error) {
    // 64ビット整数としてパースし、intに収まるかチェック
    i64, err := ParseInt(s, 10, 0)
    return int(i64), err
}

func ParseInt(s string, base int, bitSize int) (i int64, err error) {
    // 符号をチェック
    neg := false
    if s[0] == '+' {
        s = s[1:]
    } else if s[0] == '-' {
        neg = true
        s = s[1:]
    }

    // 高速パス: 10進数で64ビット以下
    var n uint64
    for _, c := range []byte(s) {
        d := c - '0'
        if d > 9 {
            return 0, &NumError{...}
        }

        // オーバーフローチェック
        if n >= cutoff {
            return 0, &NumError{Err: ErrRange}
        }

        n = n*10 + uint64(d)
    }

    if neg {
        return -int64(n), nil
    }
    return int64(n), nil
}

💡 数値変換の最適化技術:

┌──────────────────────────────────────────────────────┐
│ 高速数値パースの技術                                   │
├──────────────────────────────────────────────────────┤
│                                                      │
│ 1. ルックアップテーブル不使用                          │
│                                                      │
│    d := c - '0'  // ASCII '0' は 0x30               │
│                                                      │
│    '0': 0x30 - 0x30 = 0                            │
│    '1': 0x31 - 0x30 = 1                            │
│    ...                                               │
│    '9': 0x39 - 0x30 = 9                            │
│                                                      │
│    → CPU 1命令(SUB)で変換完了                       │
│                                                      │
│ 2. オーバーフローチェックの最適化                       │
│                                                      │
│    通常の方法(遅い):                                 │
│    if n*10+d > INT64_MAX {                          │
│        // オーバーフロー                              │
│    }                                                 │
│    → 乗算、加算、比較の3操作                          │
│                                                      │
│    最適化された方法:                                   │
│    cutoff := uint64(INT64_MAX/10 + 1)              │
│    if n >= cutoff {                                 │
│        // オーバーフロー                              │
│    }                                                 │
│    → 比較1操作のみ(cutoffは定数)                    │
│                                                      │
│ 3. SIMD を使った並列変換(大きな数値)                 │
│                                                      │
│    # 一度に8桁を処理                                  │
│    input: "12345678"                                │
│                                                      │
│    MOVQ    XMM0, [input]      # 8バイト読み込み      │
│    PSUBB   XMM0, '0'*8        # 8バイト同時に-'0'    │
│    # XMM0 = [1,2,3,4,5,6,7,8]                       │
│                                                      │
│    # 各桁に重みを掛ける                               │
│    PMULLW  XMM0, [10M,1M,100K,10K,1K,100,10,1]     │
│    # 水平加算                                         │
│    PHADDW  XMM0, XMM0                               │
│    ...                                               │
│                                                      │
│ ベンチマーク("12345678" のパース):                   │
│ - 単純ループ: 15 ns                                  │
│ - SIMD最適化: 8 ns                                   │
│ → 約2倍高速                                          │
└──────────────────────────────────────────────────────┘

FormatIntの最適化

func FormatInt(i int64, base int) string {
    if base < 2 || base > 36 {
        panic("strconv: illegal base")
    }

    // 負の数を処理
    neg := i < 0
    if neg {
        i = -i
    }

    // 小さなバッファ(スタック上)
    var buf [64 + 1]byte  // 64ビット2進数 + 符号
    pos := len(buf)

    // 下の桁から処理
    for i >= int64(base) {
        pos--
        buf[pos] = digits[i%int64(base)]
        i /= int64(base)
    }
    pos--
    buf[pos] = digits[i]

    if neg {
        pos--
        buf[pos] = '-'
    }

    return string(buf[pos:])
}

const digits = "0123456789abcdefghijklmnopqrstuvwxyz"

💡 逆順構築の利点:

┌──────────────────────────────────────────────────────┐
│ FormatInt の逆順構築アルゴリズム                        │
├──────────────────────────────────────────────────────┤
│                                                      │
│ 例: FormatInt(12345, 10)                            │
│                                                      │
│ バッファ: [64]byte (スタック上)                        │
│ pos = 64                                             │
│                                                      │
│ 反復1: i=12345                                       │
│   12345 % 10 = 5                                    │
│   pos-- → pos=63                                    │
│   buf[63] = '5'                                     │
│   i = 12345 / 10 = 1234                            │
│                                                      │
│ 反復2: i=1234                                        │
│   1234 % 10 = 4                                     │
│   pos-- → pos=62                                    │
│   buf[62] = '4'                                     │
│   i = 1234 / 10 = 123                              │
│                                                      │
│ 反復3: i=123                                         │
│   123 % 10 = 3                                      │
│   pos-- → pos=61                                    │
│   buf[61] = '3'                                     │
│   i = 123 / 10 = 12                                │
│                                                      │
│ 反復4: i=12                                          │
│   12 % 10 = 2                                       │
│   pos-- → pos=60                                    │
│   buf[60] = '2'                                     │
│   i = 12 / 10 = 1                                  │
│                                                      │
│ 反復5: i=1 (< 10)                                   │
│   pos-- → pos=59                                    │
│   buf[59] = '1'                                     │
│                                                      │
│ 結果: buf[59:64] = "12345"                          │
│ return string(buf[59:64])                           │
│                                                      │
│ 利点:                                                 │
│ 1. スタック上の固定バッファ → ヒープ不使用             │
│ 2. 文字列反転不要 → O(n)操作の削減                    │
│ 3. 最終的なコピー1回のみ                              │
│                                                      │
│ メモリ使用量:                                          │
│ - スタック: 65バイト (固定)                           │
│ - ヒープ: len(result) バイトのみ                      │
└──────────────────────────────────────────────────────┘

まとめ

🔑 本章で学んだ重要ポイント:

io パッケージ

  • Reader/Writer インターフェース: バッファ所有権が呼び出し側にあることでゼロコピー実現
  • io.Copy の最適化: WriterTo/ReaderFrom インターフェースによる sendfile(2) 活用
  • メモリ効率: スライス拡張戦略により O(n) の償却コスト

fmt パッケージ

  • リフレクション: interface{} の内部構造と型情報キャッシング
  • sync.Pool: バッファ再利用によるアロケーション削減(30-50%)
  • フォーマット処理: 型スイッチによる高速パス

strings パッケージ

  • strings.Builder: unsafe.String によるゼロコピー文字列変換
  • バッファ戦略: 2倍+必要量の成長戦略
  • SIMD 最適化: IndexByte での並列比較(16倍高速化)

encoding/json パッケージ

  • 型情報キャッシュ: 初回リフレクション後は5倍高速化
  • ステートマシン: 1バイトずつ処理によるストリーミング対応
  • エスケープ最適化: ルックアップテーブルとバッチ書き込み

time パッケージ

  • 二重時刻表現: 壁時計時刻とモノトニッククロックの使い分け
  • Per-P タイマーヒープ: ロックフリーな高速タイマー管理

strconv パッケージ

  • 数値変換: 単純な減算による高速変換
  • オーバーフロー検出: 定数比較による最適化
  • 逆順構築: スタックバッファ活用による効率的な文字列生成

次章では、これらの標準ライブラリを活用した HTTP プログラミングについて、同様に深いレベルで学びます。