自由帳

主に Web サービスのシステム基盤や運用に関する覚書です

Go でメモリアラインメントの動きを見てみたメモ

以下のようなものを観測しました。

Go で動きを見てみました。

サンプルコード

package main

import (
    "fmt"
    "unsafe"
)

type Inefficient struct {
    A uint32
    B uint64
    C uint32
}

type Efficient struct {
    A uint32
    C uint32
    B uint64
}

func checkInefficientStructMemoryAddressNumber(inefficient *Inefficient) {
    // 非効率な構造体 のアドレス
    baseAddrInefficient := uintptr(unsafe.Pointer(inefficient))
    aAddrInefficient := unsafe.Offsetof(inefficient.A)
    bAddrInefficient := unsafe.Offsetof(inefficient.B)
    cAddrInefficient := unsafe.Offsetof(inefficient.C)

    // 非効率な構造体のメモリレイアウト表示
    fmt.Printf("\n=== 非効率な構造体(合計: %d バイト)===\n", unsafe.Sizeof(Inefficient{}))
    fmt.Printf("構造体のベースアドレス: %x\n", baseAddrInefficient)
    fmt.Printf("A (uint32) のオフセット: %d バイト(アドレス: %x)\n", aAddrInefficient, baseAddrInefficient+aAddrInefficient)
    fmt.Printf("B (uint64) のオフセット: %d バイト(アドレス: %x)\n", bAddrInefficient, baseAddrInefficient+bAddrInefficient)
    fmt.Printf("C (uint32) のオフセット: %d バイト(アドレス: %x)\n", cAddrInefficient, baseAddrInefficient+cAddrInefficient)

    // メモリレイアウトの視覚化
    fmt.Println("\nメモリレイアウト:")
    visualizeMemoryLayout(baseAddrInefficient, []struct {
        name   string
        offset uintptr
        size   int
    }{
        {"A", aAddrInefficient, 4},
        {"padding", 4, 4},
        {"B", bAddrInefficient, 8},
        {"C", cAddrInefficient, 4},
        {"padding", 20, 4},
    })
}

func checkEfficientStructMemoryAddressNumber(efficient *Efficient) {
    // EfficientStruct のメンバーアドレス
    baseAddrEfficient := uintptr(unsafe.Pointer(efficient))
    bAddrEfficient := unsafe.Offsetof(efficient.B)
    aAddrEfficient := unsafe.Offsetof(efficient.A)
    cAddrEfficient := unsafe.Offsetof(efficient.C)

    // 効率的な構造体のメモリレイアウト表示
    fmt.Printf("\n=== 効率的な構造体(合計: %d バイト)===\n", unsafe.Sizeof(Efficient{}))
    fmt.Printf("構造体のベースアドレス: %x\n", baseAddrEfficient)
    fmt.Printf("B (uint64) のオフセット: %d バイト(アドレス: %x)\n", bAddrEfficient, baseAddrEfficient+bAddrEfficient)
    fmt.Printf("A (uint32) のオフセット: %d バイト(アドレス: %x)\n", aAddrEfficient, baseAddrEfficient+aAddrEfficient)
    fmt.Printf("C (uint32) のオフセット: %d バイト(アドレス: %x)\n", cAddrEfficient, baseAddrEfficient+cAddrEfficient)

    // メモリレイアウトの視覚化
    fmt.Println("\nメモリレイアウト:")
    visualizeMemoryLayout(baseAddrEfficient, []struct {
        name   string
        offset uintptr
        size   int
    }{
        {"B", bAddrEfficient, 8},
        {"A", aAddrEfficient, 4},
        {"C", cAddrEfficient, 4},
    })
}

func visualizeMemoryLayout(baseAddr uintptr, layout []struct {
    name   string
    offset uintptr
    size   int
}) {
    for _, item := range layout {
        addr := baseAddr + item.offset
        fmt.Printf("%x: ", addr)
        if item.name == "padding" {
            fmt.Printf("[%s] (%d bytes)\n", item.name, item.size)
        } else {
            fmt.Printf("%s (%d bytes)\n", item.name, item.size)
        }
    }
}

func main() {
    fmt.Printf("Inefficient構造体の1つのサイズ: %d bytes\n", unsafe.Sizeof(Inefficient{}))
    fmt.Printf("Efficient構造体の1つのサイズ: %d bytes\n\n", unsafe.Sizeof(Efficient{}))
    inefficient := &Inefficient{A: 1, B: 2, C: 3}
    efficient := &Efficient{B: 2, A: 1, C: 3}

    checkInefficientStructMemoryAddressNumber(inefficient)
    checkEfficientStructMemoryAddressNumber(efficient)
}

実行

% go run main.go

出力

Inefficient構造体の1つのサイズ: 24 bytes
Efficient構造体の1つのサイズ: 16 bytes


=== 非効率な構造体(合計: 24 バイト)===
構造体のベースアドレス: c00008ef08
A (uint32) のオフセット: 0 バイト(アドレス: c00008ef08)
B (uint64) のオフセット: 8 バイト(アドレス: c00008ef10)
C (uint32) のオフセット: 16 バイト(アドレス: c00008ef18)

メモリレイアウト:
c00008ef08: A (4 bytes)
c00008ef0c: [padding] (4 bytes)
c00008ef10: B (8 bytes)
c00008ef18: C (4 bytes)
c00008ef1c: [padding] (4 bytes)

=== 効率的な構造体(合計: 16 バイト)===
構造体のベースアドレス: c00008eef8
B (uint64) のオフセット: 0 バイト(アドレス: c00008eef8)
A (uint32) のオフセット: 8 バイト(アドレス: c00008ef00)
C (uint32) のオフセット: 12 バイト(アドレス: c00008ef04)

メモリレイアウト:
c00008eef8: B (8 bytes)
c00008ef00: A (4 bytes)
c00008ef04: C (4 bytes)

確認

メモリレイアウト:
c00008ef08: A (4 bytes)
c00008ef0c: [padding] (4 bytes)
c00008ef10: B (8 bytes)
c00008ef18: C (4 bytes)
c00008ef1c: [padding] (4 bytes)

メモリは1バイトずつ連続的にアドレスが割り当てられています。16進数で1ずつ増えていっています。

c00008ef08: A の1バイト目
c00008ef09: A の2バイト目
c00008ef0a: A の3バイト目
c00008ef0b: A の4バイト目
c00008ef0c: パディング1バイト目
c00008ef0d: パディング2バイト目
c00008ef0e: パディング3バイト目
c00008ef0f: パディング4バイト目
c00008ef10: B の1バイト目
c00008ef11: B の2バイト目
c00008ef12: B の3バイト目
c00008ef13: B の4バイト目
c00008ef14: B の5バイト目
c00008ef15: B の6バイト目
c00008ef16: B の7バイト目
c00008ef17: B の8バイト目
c00008ef18: C の1バイト目
c00008ef19: C の2バイト目
c00008ef1a: C の3バイト目
c00008ef1b: C の4バイト目
c00008ef1c: パディング1バイト目
c00008ef1d: パディング2バイト目
c00008ef1e: パディング3バイト目
c00008ef1f: パディング4バイト目
メモリレイアウト:
c00008eef8: B (8 bytes)
c00008ef00: A (4 bytes)
c00008ef04: C (4 bytes)
c00008eef8: B の1バイト目
c00008eef9: B の2バイト目
c00008eefa: B の3バイト目
c00008eefb: B の4バイト目
c00008eefc: B の5バイト目
c00008eefd: B の6バイト目
c00008eefe: B の7バイト目
c00008eeff: B の8バイト目
c00008ef00: A の1バイト目
c00008ef01: A の2バイト目
c00008ef02: A の3バイト目
c00008ef03: A の4バイト目
c00008ef04: C の1バイト目
c00008ef05: C の2バイト目
c00008ef06: C の3バイト目
c00008ef07: C の4バイト目

参考

*データ構造アライメント - Wikipedia * unsafe package - unsafe - Go Packages