Go Memory Layout

1. Apa Itu Memory Layout?

Setiap variabel di Go (dan bahasa pemrograman lain) disimpan di RAM. Bayangkan RAM seperti rak besar berisi kotak-kotak kecil (byte) yang berjejer.

Contoh:

Alamat memori (hex): 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | ...
Isi tiap kotak (byte):  ??   ??    ??    ??    ??   ...

Nah, ketika kamu punya struct seperti ini:

type SimpleStruct struct {
    a int8
    b int16
    c int32
    d int64
}

Go harus menyimpan semua field ini berurutan di memori. Tapi, CPU punya aturan alignment yang membuat tidak semua data bisa langsung ditempel rapat satu sama lain.


2. Alignment dan Padding: Kenapa Ada "Ruang Kosong"?

Alignment

Setiap tipe data di Go (dan bahasa lain seperti C) punya alignment requirement β€” yaitu bahwa data harus dimulai di alamat memori yang merupakan kelipatan tertentu.

Tipe Data
Ukuran
Alignment

int8

1 byte

1 byte

int16

2 byte

2 byte

int32

4 byte

4 byte

int64

8 byte

8 byte

Artinya:

  • int8 boleh mulai di mana saja.

  • int16 harus mulai di alamat yang kelipatan 2 (misalnya 0, 2, 4, 6, dst).

  • int32 harus mulai di alamat yang kelipatan 4.

  • int64 harus mulai di alamat yang kelipatan 8.

Kalau posisi field berikutnya tidak sesuai alignment-nya, Go akan menyisipkan padding byte kosong supaya tetap sejajar dengan aturan CPU.


3. Visualisasi Step-by-Step Memory Layout

Kita lihat layout struct berikut di sistem 64-bit:

type SimpleStruct struct {
    a int8   // 1 byte
    b int16  // 2 byte
    c int32  // 4 byte
    d int64  // 8 byte
}

Field a (int8)

  • Ukuran: 1 byte

  • Alignment: 1 byte

  • Diletakkan mulai di alamat 0.

Byte 0: [a] = int8

Field b (int16)

  • Ukuran: 2 byte

  • Alignment: 2 byte

  • Field sebelumnya (a) berakhir di byte ke-1 β†’ byte berikutnya adalah 1, bukan kelipatan 2 β†’ harus ada padding 1 byte.

Byte 0: [a] (1 byte)
Byte 1: [padding]
Byte 2-3: [b] = int16

Field c (int32)

  • Ukuran: 4 byte

  • Alignment: 4 byte

  • Field b berakhir di byte 3 β†’ byte berikutnya adalah 4, sudah kelipatan 4, jadi tidak perlu padding.

Byte 4-7: [c] = int32

Field d (int64)

  • Ukuran: 8 byte

  • Alignment: 8 byte

  • Field c berakhir di byte 7, jadi byte berikutnya 8, sudah kelipatan 8, tidak perlu padding di sini.

Byte 8-15: [d] = int64

Kenapa Masih Ada Padding di Akhir?

Struct di Go juga disesuaikan agar ukuran total struct adalah kelipatan dari alignment terbesar di dalam struct (di sini: 8 byte). Tujuannya agar ketika array of struct dibuat, setiap struct tetap sejajar secara optimal di memori.

Sekarang total byte kita baru sampai byte 15. Supaya kelipatan 8, Go menambahkan 8 byte padding di akhir β†’ jadi total 24 byte.


4. Total Size Perhitungan

Bagian
Byte

a (int8)

1

padding (setelah a)

1

b (int16)

2

c (int32)

4

d (int64)

8

padding (akhir struct)

8

Total

24 byte


5. Kenapa Bukan 15 Byte?

Kalau dihitung mentah:

1 (a) + 2 (b) + 4 (c) + 8 (d) = 15

Tapi komputer tidak boleh menyimpan sembarangan di memori tanpa memperhatikan alignment. Kalau kamu β€œmaksa”, CPU harus melakukan operasi baca/tulis byte demi byte (bukan per word) β€” ini membuatnya lebih lambat bahkan bisa crash di beberapa arsitektur.

Jadi, Go menambahkan padding otomatis supaya semuanya sejajar, efisien, dan aman bagi CPU.


6. Visualisasi Akhir (64-bit)

Memory Layout (byte offset):
| Offset | Size | Field     | Note                |
|---------|------|------------|--------------------|
| 0       | 1    | a (int8)  |                    |
| 1       | 1    | padding   | align b (int16)    |
| 2-3     | 2    | b (int16) |                    |
| 4-7     | 4    | c (int32) |                    |
| 8-15    | 8    | d (int64) |                    |
| 16-23   | 8    | padding   | struct alignment   |
| **Total** | **24** | **bytes** |                    |

7. Tips: Cara Mengecek Sendiri di Go

Kamu bisa lihat ukuran dan offset tiap field pakai unsafe:

package main

import (
    "fmt"
    "unsafe"
)

type SimpleStruct struct {
    a int8
    b int16
    c int32
    d int64
}

func main() {
    var s SimpleStruct
    fmt.Println("Size:", unsafe.Sizeof(s))
    fmt.Println("Offset a:", unsafe.Offsetof(s.a))
    fmt.Println("Offset b:", unsafe.Offsetof(s.b))
    fmt.Println("Offset c:", unsafe.Offsetof(s.c))
    fmt.Println("Offset d:", unsafe.Offsetof(s.d))
}

Outputnya akan kira-kira:

Size: 24
Offset a: 0
Offset b: 2
Offset c: 4
Offset d: 8

8. Bonus: Mengurangi Ukuran Struct

Kalau kamu urutkan field dari yang besar ke kecil, padding bisa berkurang:

type CompactStruct struct {
    d int64
    c int32
    b int16
    a int8
}

Ini bisa membuat total hanya 16 byte, bukan 24, karena padding di tengah bisa dihindari.


Ringkasan

Konsep
Penjelasan

Memory layout

Urutan field struct di memori

Alignment

Data harus dimulai di alamat kelipatan ukuran tertentu

Padding

Byte kosong untuk menjaga alignment

Ukuran struct

Dibulatkan ke kelipatan alignment terbesar

Optimasi

Urutkan field dari besar ke kecil agar lebih rapat


Tutorial Lengkap Memory Layout di Go

Dari Dasar Hingga Advanced dengan Praktik Kode


Daftar Isi


1. Pengenalan Memory Layout

1.1 Apa itu Memory Layout?

Memory layout adalah cara kompiler Go mengatur dan menempatkan data dalam memori. Pemahaman tentang memory layout sangat penting untuk:

  • Optimisasi penggunaan memori - Menghemat RAM dengan mengurangi padding

  • Peningkatan performa - CPU lebih efisien mengakses data yang aligned dengan benar

  • Keamanan concurrent programming - Penggunaan atomic operations yang tepat

  • Debugging - Memahami ukuran sebenarnya dari struct

1.2 Konsep Fundamental

Alignment (Penjajaran)

Alamat memori tempat data dimulai harus merupakan kelipatan dari nilai tertentu (N). Nilai N ini disebut alignment guarantee.

Padding (Isian)

Byte-byte tambahan yang disisipkan oleh kompiler untuk memenuhi aturan alignment.

Size (Ukuran)

Total memori yang digunakan oleh sebuah tipe data, termasuk padding.

1.3 Kode Dasar: Memeriksa Size dan Alignment

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    fmt.Println("=== UKURAN TIPE DASAR ===")
    
    // Tipe integer
    fmt.Printf("int8:   %d byte\n", unsafe.Sizeof(int8(0)))
    fmt.Printf("int16:  %d byte\n", unsafe.Sizeof(int16(0)))
    fmt.Printf("int32:  %d byte\n", unsafe.Sizeof(int32(0)))
    fmt.Printf("int64:  %d byte\n", unsafe.Sizeof(int64(0)))
    
    // Tipe unsigned integer
    fmt.Printf("uint8:  %d byte\n", unsafe.Sizeof(uint8(0)))
    fmt.Printf("uint16: %d byte\n", unsafe.Sizeof(uint16(0)))
    fmt.Printf("uint32: %d byte\n", unsafe.Sizeof(uint32(0)))
    fmt.Printf("uint64: %d byte\n", unsafe.Sizeof(uint64(0)))
    
    // Tipe floating point
    fmt.Printf("float32: %d byte\n", unsafe.Sizeof(float32(0)))
    fmt.Printf("float64: %d byte\n", unsafe.Sizeof(float64(0)))
    
    // Tipe complex
    fmt.Printf("complex64:  %d byte\n", unsafe.Sizeof(complex64(0)))
    fmt.Printf("complex128: %d byte\n", unsafe.Sizeof(complex128(0)))
    
    // Tipe boolean
    fmt.Printf("bool: %d byte\n", unsafe.Sizeof(bool(true)))
    
    fmt.Println("\n=== ALIGNMENT TIPE DASAR ===")
    
    fmt.Printf("int8:   %d byte\n", unsafe.Alignof(int8(0)))
    fmt.Printf("int16:  %d byte\n", unsafe.Alignof(int16(0)))
    fmt.Printf("int32:  %d byte\n", unsafe.Alignof(int32(0)))
    fmt.Printf("int64:  %d byte\n", unsafe.Alignof(int64(0)))
    fmt.Printf("float32: %d byte\n", unsafe.Alignof(float32(0)))
    fmt.Printf("float64: %d byte\n", unsafe.Alignof(float64(0)))
}

Output (pada arsitektur 64-bit):

=== UKURAN TIPE DASAR ===
int8:   1 byte
int16:  2 byte
int32:  4 byte
int64:  8 byte
uint8:  1 byte
uint16: 2 byte
uint32: 4 byte
uint64: 8 byte
float32: 4 byte
float64: 8 byte
complex64:  8 byte
complex128: 16 byte
bool: 1 byte

=== ALIGNMENT TIPE DASAR ===
int8:   1 byte
int16:  2 byte
int32:  4 byte
int64:  8 byte
float32: 4 byte
float64: 8 byte

2. Type Alignment Guarantees

2.1 Aturan Alignment di Go

Go memiliki aturan alignment yang berbeda untuk setiap tipe data:

Tipe Data
Alignment (bytes)

bool, int8, uint8

1

int16, uint16

2

int32, uint32, float32

4

int64, uint64, float64, complex64

8 (64-bit) / 4 (32-bit)

complex128

8

pointer, string, slice, map, channel

8 (64-bit) / 4 (32-bit)

array

Sama dengan elemen array

struct

Alignment terbesar dari field-fieldnya

2.2 General vs Field Alignment

Ada dua jenis alignment guarantee:

  1. General Alignment: Digunakan untuk variable declaration, array elements, dll.

  2. Field Alignment: Digunakan ketika tipe tersebut menjadi field dari struct.

Pada compiler standar Go (gc), kedua nilai ini selalu sama. Namun pada gccgo, bisa berbeda.

2.3 Kode: Memeriksa Alignment Detail

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

type SimpleStruct struct {
    a int8
    b int16
    c int32
    d int64
}

func main() {
    var s SimpleStruct
    
    fmt.Println("=== STRUCT ALIGNMENT ===")
    fmt.Printf("Struct alignment: %d bytes\n", unsafe.Alignof(s))
    fmt.Printf("Struct size: %d bytes\n\n", unsafe.Sizeof(s))
    
    // Menggunakan unsafe package
    fmt.Println("=== FIELD ANALYSIS (unsafe) ===")
    fmt.Printf("Field a: offset=%d, size=%d, align=%d\n",
        unsafe.Offsetof(s.a), unsafe.Sizeof(s.a), unsafe.Alignof(s.a))
    fmt.Printf("Field b: offset=%d, size=%d, align=%d\n",
        unsafe.Offsetof(s.b), unsafe.Sizeof(s.b), unsafe.Alignof(s.b))
    fmt.Printf("Field c: offset=%d, size=%d, align=%d\n",
        unsafe.Offsetof(s.c), unsafe.Sizeof(s.c), unsafe.Alignof(s.c))
    fmt.Printf("Field d: offset=%d, size=%d, align=%d\n",
        unsafe.Offsetof(s.d), unsafe.Sizeof(s.d), unsafe.Alignof(s.d))
    
    // Menggunakan reflect package
    fmt.Println("\n=== FIELD ANALYSIS (reflect) ===")
    t := reflect.TypeOf(s)
    fmt.Printf("Struct align: %d\n", t.Align())
    fmt.Printf("Struct field align: %d\n", t.FieldAlign())
    
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Printf("Field %s: offset=%d, size=%d\n",
            field.Name, field.Offset, field.Type.Size())
    }
}

Output:

=== STRUCT ALIGNMENT ===
Struct alignment: 8 bytes
Struct size: 24 bytes

=== FIELD ANALYSIS (unsafe) ===
Field a: offset=0, size=1, align=1
Field b: offset=2, size=2, align=2
Field c: offset=4, size=4, align=4
Field d: offset=8, size=8, align=8

=== FIELD ANALYSIS (reflect) ===
Struct align: 8
Struct field align: 8
Field a: offset=0, size=1
Field b: offset=2, size=2
Field c: offset=4, size=4
Field d: offset=8, size=8

2.4 Visualisasi Memory Layout

Memory Address Layout untuk SimpleStruct (64-bit):

Byte 0:     [a] int8 (1 byte)
Byte 1:     [padding] (1 byte)
Byte 2-3:   [b] int16 (2 bytes)
Byte 4-7:   [c] int32 (4 bytes)
Byte 8-15:  [d] int64 (8 bytes)
Byte 16-23: [padding] (8 bytes) - untuk memenuhi alignment 8

Total: 24 bytes (bukan 15 bytes = 1+2+4+8)

3. Type Sizes dan Structure Padding

3.1 Mengapa Structure Padding Penting?

Padding memastikan setiap field memulai pada alamat yang sesuai dengan alignment requirement-nya. Urutan field sangat mempengaruhi ukuran total struct!

3.2 Contoh: Dampak Urutan Field

package main

import (
    "fmt"
    "unsafe"
)

// Layout BURUK - banyak padding
type BadLayout struct {
    a int8   // 1 byte
    // 7 bytes padding (untuk alignment int64)
    b int64  // 8 bytes
    c int16  // 2 bytes
    // 6 bytes padding (untuk alignment struct = 8)
}

// Layout BAIK - minimal padding
type GoodLayout struct {
    b int64  // 8 bytes
    c int16  // 2 bytes
    a int8   // 1 byte
    // 5 bytes padding (untuk alignment struct = 8)
}

// Layout OPTIMAL - field sejenis dikelompokkan
type OptimalLayout struct {
    b int64  // 8 bytes
    c int16  // 2 bytes
    a int8   // 1 byte
    // 5 bytes padding
}

func main() {
    fmt.Println("=== PERBANDINGAN LAYOUT ===")
    fmt.Printf("BadLayout size:     %d bytes\n", unsafe.Sizeof(BadLayout{}))
    fmt.Printf("GoodLayout size:    %d bytes\n", unsafe.Sizeof(GoodLayout{}))
    fmt.Printf("OptimalLayout size: %d bytes\n", unsafe.Sizeof(OptimalLayout{}))
    
    // Detail BadLayout
    var bad BadLayout
    fmt.Println("\n--- BadLayout Detail (24 bytes) ---")
    fmt.Printf("Field a: offset=%2d, size=%d, align=%d\n",
        unsafe.Offsetof(bad.a), unsafe.Sizeof(bad.a), unsafe.Alignof(bad.a))
    fmt.Printf("Field b: offset=%2d, size=%d, align=%d\n",
        unsafe.Offsetof(bad.b), unsafe.Sizeof(bad.b), unsafe.Alignof(bad.b))
    fmt.Printf("Field c: offset=%2d, size=%d, align=%d\n",
        unsafe.Offsetof(bad.c), unsafe.Sizeof(bad.c), unsafe.Alignof(bad.c))
    
    actualSize := unsafe.Sizeof(bad.a) + unsafe.Sizeof(bad.b) + unsafe.Sizeof(bad.c)
    padding := unsafe.Sizeof(bad) - actualSize
    fmt.Printf("Actual fields: %d bytes, Padding: %d bytes (%.1f%%)\n",
        actualSize, padding, float64(padding)/float64(unsafe.Sizeof(bad))*100)
    
    // Detail GoodLayout
    var good GoodLayout
    fmt.Println("\n--- GoodLayout Detail (16 bytes) ---")
    fmt.Printf("Field b: offset=%2d, size=%d, align=%d\n",
        unsafe.Offsetof(good.b), unsafe.Sizeof(good.b), unsafe.Alignof(good.b))
    fmt.Printf("Field c: offset=%2d, size=%d, align=%d\n",
        unsafe.Offsetof(good.c), unsafe.Sizeof(good.c), unsafe.Alignof(good.c))
    fmt.Printf("Field a: offset=%2d, size=%d, align=%d\n",
        unsafe.Offsetof(good.a), unsafe.Sizeof(good.a), unsafe.Alignof(good.a))
    
    actualSize = unsafe.Sizeof(good.a) + unsafe.Sizeof(good.b) + unsafe.Sizeof(good.c)
    padding = unsafe.Sizeof(good) - actualSize
    fmt.Printf("Actual fields: %d bytes, Padding: %d bytes (%.1f%%)\n",
        actualSize, padding, float64(padding)/float64(unsafe.Sizeof(good))*100)
    
    fmt.Printf("\nπŸ’° Memory savings: %d bytes (%.1f%% reduction)\n",
        unsafe.Sizeof(bad)-unsafe.Sizeof(good),
        float64(unsafe.Sizeof(bad)-unsafe.Sizeof(good))/float64(unsafe.Sizeof(bad))*100)
}

Output:

=== PERBANDINGAN LAYOUT ===
BadLayout size:     24 bytes
GoodLayout size:    16 bytes
OptimalLayout size: 16 bytes

--- BadLayout Detail (24 bytes) ---
Field a: offset= 0, size=1, align=1
Field b: offset= 8, size=8, align=8
Field c: offset=16, size=2, align=2
Actual fields: 11 bytes, Padding: 13 bytes (54.2%)

--- GoodLayout Detail (16 bytes) ---
Field b: offset= 0, size=8, align=8
Field c: offset= 8, size=2, align=2
Field a: offset=10, size=1, align=1
Actual fields: 11 bytes, Padding: 5 bytes (31.2%)

πŸ’° Memory savings: 8 bytes (33.3% reduction)

3.3 Visualisasi Memory Layout

BadLayout (24 bytes):
β”Œβ”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  a  β”‚   padding (7)   β”‚    b    β”‚   c    β”‚  padding (6)  β”‚
β”‚ 1B  β”‚      7B         β”‚   8B    β”‚  2B    β”‚      6B       β”‚
β””β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Offset: 0     1           8        16      18              24

GoodLayout (16 bytes):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    b    β”‚   c    β”‚  a  β”‚  padding (5) β”‚
β”‚   8B    β”‚  2B    β”‚ 1B  β”‚     5B       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Offset: 0        8      10  11          16

3.4 Kode: Analyzer Tool untuk Struct

package main

import (
    "fmt"
    "unsafe"
)

type StructAnalyzer struct {
    name      string
    totalSize uintptr
    fields    []FieldInfo
}

type FieldInfo struct {
    name   string
    offset uintptr
    size   uintptr
    align  uintptr
}

func analyzeStruct(name string, s interface{}, fields []FieldInfo) {
    analyzer := StructAnalyzer{
        name:      name,
        totalSize: reflect.TypeOf(s).Size(),
        fields:    fields,
    }
    
    fmt.Printf("\n=== %s Analysis ===\n", analyzer.name)
    fmt.Printf("Total Size: %d bytes\n\n", analyzer.totalSize)
    
    fmt.Println("Field Details:")
    fmt.Println("Name           Offset  Size  Align")
    fmt.Println("─────────────  ──────  ────  ─────")
    
    actualSize := uintptr(0)
    for _, f := range analyzer.fields {
        fmt.Printf("%-13s  %6d  %4d  %5d\n", f.name, f.offset, f.size, f.align)
        actualSize += f.size
    }
    
    padding := analyzer.totalSize - actualSize
    paddingPct := float64(padding) / float64(analyzer.totalSize) * 100
    
    fmt.Println("─────────────  ──────  ────  ─────")
    fmt.Printf("Field Total:          %4d bytes\n", actualSize)
    fmt.Printf("Padding:              %4d bytes (%.1f%%)\n", padding, paddingPct)
    fmt.Printf("Struct Total:         %4d bytes\n", analyzer.totalSize)
}

// Contoh struct untuk dianalisis
type ExampleStruct struct {
    a bool    // 1 byte
    b int32   // 4 bytes
    c bool    // 1 byte
    d int64   // 8 bytes
    e bool    // 1 byte
    f int16   // 2 bytes
}

func main() {
    var ex ExampleStruct
    
    fields := []FieldInfo{
        {"a (bool)", unsafe.Offsetof(ex.a), unsafe.Sizeof(ex.a), unsafe.Alignof(ex.a)},
        {"b (int32)", unsafe.Offsetof(ex.b), unsafe.Sizeof(ex.b), unsafe.Alignof(ex.b)},
        {"c (bool)", unsafe.Offsetof(ex.c), unsafe.Sizeof(ex.c), unsafe.Alignof(ex.c)},
        {"d (int64)", unsafe.Offsetof(ex.d), unsafe.Sizeof(ex.d), unsafe.Alignof(ex.d)},
        {"e (bool)", unsafe.Offsetof(ex.e), unsafe.Sizeof(ex.e), unsafe.Alignof(ex.e)},
        {"f (int16)", unsafe.Offsetof(ex.f), unsafe.Sizeof(ex.f), unsafe.Alignof(ex.f)},
    }
    
    analyzeStruct("ExampleStruct", ex, fields)
    
    // Sekarang buat versi optimal
    type OptimizedStruct struct {
        d int64   // 8 bytes
        b int32   // 4 bytes
        f int16   // 2 bytes
        a bool    // 1 byte
        c bool    // 1 byte
        e bool    // 1 byte
    }
    
    var opt OptimizedStruct
    fieldsOpt := []FieldInfo{
        {"d (int64)", unsafe.Offsetof(opt.d), unsafe.Sizeof(opt.d), unsafe.Alignof(opt.d)},
        {"b (int32)", unsafe.Offsetof(opt.b), unsafe.Sizeof(opt.b), unsafe.Alignof(opt.b)},
        {"f (int16)", unsafe.Offsetof(opt.f), unsafe.Sizeof(opt.f), unsafe.Alignof(opt.f)},
        {"a (bool)", unsafe.Offsetof(opt.a), unsafe.Sizeof(opt.a), unsafe.Alignof(opt.a)},
        {"c (bool)", unsafe.Offsetof(opt.c), unsafe.Sizeof(opt.c), unsafe.Alignof(opt.c)},
        {"e (bool)", unsafe.Offsetof(opt.e), unsafe.Sizeof(opt.e), unsafe.Alignof(opt.e)},
    }
    
    analyzeStruct("OptimizedStruct", opt, fieldsOpt)
    
    fmt.Printf("\nπŸ’‘ Optimization Result:\n")
    fmt.Printf("Original:  %d bytes\n", unsafe.Sizeof(ex))
    fmt.Printf("Optimized: %d bytes\n", unsafe.Sizeof(opt))
    fmt.Printf("Savings:   %d bytes (%.1f%% reduction)\n",
        unsafe.Sizeof(ex)-unsafe.Sizeof(opt),
        float64(unsafe.Sizeof(ex)-unsafe.Sizeof(opt))/float64(unsafe.Sizeof(ex))*100)
}

3.5 Zero-Sized Fields

Field dengan ukuran 0 byte (seperti struct{} atau [0]int) kadang dapat mempengaruhi padding:

package main

import (
    "fmt"
    "unsafe"
)

type WithoutZeroField struct {
    a int32
    b int32
}

type WithZeroField struct {
    a int32
    _ struct{} // zero-sized field
    b int32
}

type WithZeroArray struct {
    a int32
    _ [0]int64 // zero-sized array, alignment = 8
    b int32
}

func main() {
    fmt.Println("=== ZERO-SIZED FIELDS EFFECT ===")
    fmt.Printf("WithoutZeroField: %d bytes\n", unsafe.Sizeof(WithoutZeroField{}))
    fmt.Printf("WithZeroField:    %d bytes\n", unsafe.Sizeof(WithZeroField{}))
    fmt.Printf("WithZeroArray:    %d bytes\n", unsafe.Sizeof(WithZeroArray{}))
    
    var wza WithZeroArray
    fmt.Printf("\nWithZeroArray details:\n")
    fmt.Printf("Field a offset: %d\n", unsafe.Offsetof(wza.a))
    fmt.Printf("Field b offset: %d\n", unsafe.Offsetof(wza.b))
}

Output:

=== ZERO-SIZED FIELDS EFFECT ===
WithoutZeroField: 8 bytes
WithZeroField:    8 bytes
WithZeroArray:    16 bytes

WithZeroArray details:
Field a offset: 0
Field b offset: 8

4. Alignment untuk 64-bit Atomic Operations

4.1 Masalah pada Arsitektur 32-bit

Pada arsitektur 32-bit, alignment guarantee untuk int64 dan uint64 hanya 4 bytes, tetapi operasi atomic 64-bit memerlukan alignment 8 bytes. Jika tidak terpenuhi, program akan panic di runtime!

4.2 Aturan Keamanan

Menurut dokumentasi sync/atomic:

On 32-bit systems (ARM, x86-32), the 64-bit functions use instructions unavailable before certain CPU generations.

It is the caller's responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically. The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned.

4.3 Kata yang Aman untuk Atomic Operations

Pada arsitektur 32-bit, 64-bit word berikut dijamin 8-byte aligned:

  1. Variable global

  2. Variable lokal yang dideklarasikan dengan var

  3. Elemen pertama dari allocated array/slice

  4. Field pertama dari allocated struct

  5. Value yang dikembalikan oleh new() atau make()

4.4 Contoh: Safe vs Unsafe Atomic Access

package main

import (
    "fmt"
    "sync/atomic"
    "unsafe"
)

type SafeCounter struct {
    value uint64 // Field pertama - AMAN
}

type UnsafeCounter struct {
    _     int16  // Field lain di depan
    value uint64 // Bukan field pertama - TIDAK AMAN di 32-bit!
}

type MultiCounter struct {
    _      int16
    value1 uint64 // TIDAK AMAN di 32-bit
    value2 *uint64 // Pointer aman, tapi yang ditunjuk harus allocated
}

func main() {
    fmt.Println("=== ATOMIC SAFETY ANALYSIS (64-bit arch) ===\n")
    
    // 1. Global variable - AMAN
    var globalCounter uint64
    fmt.Printf("1. Global variable:\n")
    fmt.Printf("   Address: %p, Alignment: %d\n", 
        &globalCounter, unsafe.Alignof(globalCounter))
    atomic.AddUint64(&globalCounter, 1)
    fmt.Printf("   βœ“ Safe for atomic operations\n\n")
    
    // 2. Local variable - AMAN
    var localCounter uint64
    fmt.Printf("2. Local variable:\n")
    fmt.Printf("   Address: %p, Alignment: %d\n", 
        &localCounter, unsafe.Alignof(localCounter))
    atomic.AddUint64(&localCounter, 1)
    fmt.Printf("   βœ“ Safe for atomic operations\n\n")
    
    // 3. SafeCounter - AMAN (field pertama)
    var safe SafeCounter
    fmt.Printf("3. SafeCounter.value (first field):\n")
    fmt.Printf("   Address: %p, Offset: %d\n", 
        &safe.value, unsafe.Offsetof(safe.value))
    atomic.AddUint64(&safe.value, 1)
    fmt.Printf("   βœ“ Safe for atomic operations\n\n")
    
    // 4. UnsafeCounter - TIDAK AMAN di 32-bit (bukan field pertama)
    var unsafe1 UnsafeCounter
    fmt.Printf("4. UnsafeCounter.value (NOT first field):\n")
    fmt.Printf("   Address: %p, Offset: %d\n", 
        &unsafe1.value, unsafe.Offsetof(unsafe1.value))
    fmt.Printf("   ⚠️  UNSAFE on 32-bit architectures!\n\n")
    
    // 5. Array - AMAN (elemen pertama)
    var arr [5]uint64
    fmt.Printf("5. Array element [0]:\n")
    fmt.Printf("   Address: %p\n", &arr[0])
    atomic.AddUint64(&arr[0], 1)
    fmt.Printf("   βœ“ Safe for atomic operations\n\n")
    
    // 6. Slice dari array - AMAN jika elemen pertama = array pertama
    slice := arr[:]
    fmt.Printf("6. Slice from array:\n")
    fmt.Printf("   Address: %p\n", &slice[0])
    atomic.AddUint64(&slice[0], 1)
    fmt.Printf("   βœ“ Safe for atomic operations\n\n")
    
    // 7. Allocated struct dengan new() - AMAN
    safePtr := new(SafeCounter)
    fmt.Printf("7. Allocated struct (new):\n")
    fmt.Printf("   Address: %p\n", &safePtr.value)
    atomic.AddUint64(&safePtr.value, 1)
    fmt.Printf("   βœ“ Safe for atomic operations\n\n")
    
    // 8. MultiCounter dengan allocated value - AMAN
    var multi MultiCounter
    multi.value2 = new(uint64)
    fmt.Printf("8. MultiCounter with allocated pointer:\n")
    fmt.Printf("   Address: %p\n", multi.value2)
    atomic.AddUint64(multi.value2, 1)
    fmt.Printf("   βœ“ Safe for atomic operations\n\n")
}

4.5 Solusi untuk Arsitektur 32-bit

Solusi 1: Gunakan Field Pertama

package main

import (
    "sync/atomic"
)

// βœ“ AMAN - value adalah field pertama
type SafeCounter struct {
    value uint64
    name  string
    id    int32
}

func (c *SafeCounter) Increment() {
    atomic.AddUint64(&c.value, 1)
}

func (c *SafeCounter) Get() uint64 {
    return atomic.LoadUint64(&c.value)
}

Solusi 2: Gunakan Array Alignment Trick

package main

import (
    "sync/atomic"
    "unsafe"
)

// Counter yang selalu aman dengan alignment trick
type AlignedCounter struct {
    x [15]byte // Bukan uint64!
}

func (c *AlignedCounter) valueAddr() *uint64 {
    // Hitung alamat yang dijamin 8-byte aligned
    addr := uintptr(unsafe.Pointer(&c.x))
    alignedAddr := (addr + 7) &^ 7 // Round up ke kelipatan 8
    return (*uint64)(unsafe.Pointer(alignedAddr))
}

func (c *AlignedCounter) Add(delta uint64) {
    atomic.AddUint64(c.valueAddr(), delta)
}

func (c *AlignedCounter) Get() uint64 {
    return atomic.LoadUint64(c.valueAddr())
}

// Bisa di-embed dengan aman
type MyService struct {
    name    string
    counter AlignedCounter // Aman meski bukan field pertama!
    active  bool
}

Solusi 3: Gunakan atomic.Int64/Uint64 (Go 1.19+)

package main

import (
    "sync/atomic"
)

// βœ“ TERBAIK - Menggunakan atomic types (Go 1.19+)
// atomic.Uint64 dijamin 8-byte aligned di semua arsitektur
type ModernCounter struct {
    name  string
    value atomic.Uint64 // Selalu aman!
    id    int32
}

func (c *ModernCounter) Increment() {
    c.value.Add(1)
}

func (c *ModernCounter) Get() uint64 {
    return c.value.Load()
}

// Cara lain dengan embedded field
type AnotherCounter struct {
    _ [0]atomic.Int64 // Zero-sized, tapi memaksa alignment 8
    value int64       // Sekarang dijamin 8-byte aligned
    name  string
}

func (c *AnotherCounter) Increment() {
    atomic.AddInt64(&c.value, 1)
}

4.6 Testing Atomic Safety

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
    "testing"
)

type TestCounter struct {
    value uint64
}

func TestAtomicSafety(t *testing.T) {
    const numGoroutines = 100
    const incrementsPerGoroutine = 1000
    
    var counter TestCounter
    var wg sync.WaitGroup
    
    // Test dengan concurrent access
    for i := 0; i < numGoroutines; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < incrementsPerGoroutine; j++ {
                atomic.AddUint64(&counter.value, 1)
            }
        }()
    }
    
    wg.Wait()
    
    expected := uint64(numGoroutines * incrementsPerGoroutine)
    actual := atomic.LoadUint64(&counter.value)
    
    if actual != expected {
        t.Errorf("Expected %d, got %d", expected, actual)
    } else {
        fmt.Printf("βœ“ Atomic operations safe: %d increments\n", actual)
    }
}

func main() {
    t := &testing.T{}
    TestAtomicSafety(t)
}

5. Optimisasi Memory Layout

5.1 Prinsip Optimisasi

Untuk mengoptimalkan memory layout:

  1. Urutkan field dari besar ke kecil (descending by size)

  2. Kelompokkan field dengan ukuran sama

  3. Letakkan field yang sering diakses bersamaan berdekatan (cache locality)

  4. Pertimbangkan false sharing untuk concurrent access

5.2 Contoh Optimisasi Lengkap

package main

import (
    "fmt"
    "unsafe"
)

// ❌ SEBELUM OPTIMISASI - 56 bytes
type UserBefore struct {
    Active    bool      // 1 byte
    Age       int32     // 4 bytes
    Premium   bool      // 1 byte
    Balance   float64   // 8 bytes
    ID        int64     // 8 bytes
    Level     int16     // 2 bytes
    Verified  bool      // 1 byte
    Score     float32   // 4 bytes
    LastLogin int64     // 8 bytes
}

// βœ“ SETELAH OPTIMISASI - 40 bytes
type UserAfter struct {
    // 8-byte aligned fields first
    ID        int64     // 8 bytes
    Balance   float64   // 8 bytes
    LastLogin int64     // 8 bytes
    
    // 4-byte aligned fields
    Age       int32     // 4 bytes
    Score     float32   // 4 bytes
    
    // 2-byte aligned fields
    Level     int16     // 2 bytes
    
    // 1-byte aligned fields (bisa dikelompokkan)
    Active    bool      // 1 byte
    Premium   bool      // 1 byte
    Verified  bool      // 1 byte
    // 5 bytes padding untuk alignment 8
}

func compareLayouts() {
    var before UserBefore
    var after UserAfter
    
    fmt.Println("=== OPTIMISASI MEMORY LAYOUT ===\n")
    
    // Before
    fmt.Println("BEFORE OPTIMIZATION:")
    fmt.Printf("Size: %d bytes\n", unsafe.Sizeof(before))
    fmt.Println("\nField Layout:")
    fmt.Printf("Active:    offset=%2d, size=%d\n", unsafe.Offsetof(before.Active), unsafe.Sizeof(before.Active))
    fmt.Printf("Age:       offset=%2d, size=%d\n", unsafe.Offsetof(before.Age), unsafe.Sizeof(before.Age))
    fmt.Printf("Premium:   offset=%2d, size=%d\n", unsafe.Offsetof(before.Premium), unsafe.Sizeof(before.Premium))
    fmt.Printf("Balance:   offset=%2d, size=%d\n", unsafe.Offsetof(before.Balance), unsafe.Sizeof(before.Balance))
    fmt.Printf("ID:        offset=%2d, size=%d\n", unsafe.Offsetof(before.ID), unsafe.Sizeof(before.ID))
    fmt.Printf("Level:     offset=%2d, size=%d\n", unsafe.Offsetof(before.Level), unsafe.Sizeof(before.Level))
    fmt.Printf("Verified:  offset=%2d, size=%d\n", unsafe.Offsetof(before.Verified), unsafe.Sizeof(before.Verified))
    fmt.Printf("Score:     offset=%2d, size=%d\n", unsafe.Offsetof(before.Score), unsafe.Sizeof(before.Score))
    fmt.Printf("LastLogin: offset=%2d, size=%d\n", unsafe.Offsetof(before.LastLogin), unsafe.Sizeof(before.LastLogin))
    
    // After
    fmt.Println("\nAFTER OPTIMIZATION:")
    fmt.Printf("Size: %d bytes\n", unsafe.Sizeof(after))
    fmt.Println("\nField Layout:")
    fmt.Printf("ID:        offset=%2d, size=%d\n", unsafe.Offsetof(after.ID), unsafe.Sizeof(after.ID))
    fmt.Printf("Balance:   offset=%2d, size=%d\n", unsafe.Offsetof(after.Balance), unsafe.Sizeof(after.Balance))
    fmt.Printf("LastLogin: offset=%2d, size=%d\n", unsafe.Offsetof(after.LastLogin), unsafe.Sizeof(after.LastLogin))
    fmt.Printf("Age:       offset=%2d, size=%d\n", unsafe.Offsetof(after.Age), unsafe.Sizeof(after.Age))
    fmt.Printf("Score:     offset=%2d, size=%d\n", unsafe.Offsetof(after.Score), unsafe.Sizeof(after.Score))
    fmt.Printf("Level:     offset=%2d, size=%d\n", unsafe.Offsetof(after.Level), unsafe.Sizeof(after.Level))
    fmt.Printf("Active:    offset=%2d, size=%d\n", unsafe.Offsetof(after.Active), unsafe.Sizeof(after.Active))
    fmt.Printf("Premium:   offset=%2d, size=%d\n", unsafe.Offsetof(after.Premium), unsafe.Sizeof(after.Premium))
    fmt.Printf("Verified:  offset=%2d, size=%d\n", unsafe.Offsetof(after.Verified), unsafe.Sizeof(after.Verified))
    
    // Savings
    savings := int(unsafe.Sizeof(before)) - int(unsafe.Sizeof(after))
    savingsPct := float64(savings) / float64(unsafe.Sizeof(before)) * 100
    
    fmt.Printf("\nπŸ’° MEMORY SAVINGS:\n")
    fmt.Printf("Before:  %d bytes\n", unsafe.Sizeof(before))
    fmt.Printf("After:   %d bytes\n", unsafe.Sizeof(after))
    fmt.Printf("Saved:   %d bytes (%.1f%% reduction)\n", savings, savingsPct)
    
    // Untuk 1 juta instances
    fmt.Printf("\nπŸ“Š For 1 million instances:\n")
    fmt.Printf("Before:  %.2f MB\n", float64(unsafe.Sizeof(before)*1_000_000)/(1024*1024))
    fmt.Printf("After:   %.2f MB\n", float64(unsafe.Sizeof(after)*1_000_000)/(1024*1024))
    fmt.Printf("Saved:   %.2f MB\n", float64(savings*1_000_000)/(1024*1024))
}

func main() {
    compareLayouts()
}

Output:

=== OPTIMISASI MEMORY LAYOUT ===

BEFORE OPTIMIZATION:
Size: 56 bytes

Field Layout:
Active:    offset= 0, size=1
Age:       offset= 4, size=4
Premium:   offset= 8, size=1
Balance:   offset=16, size=8
ID:        offset=24, size=8
Level:     offset=32, size=2
Verified:  offset=34, size=1
Score:     offset=36, size=4
LastLogin: offset=40, size=8

AFTER OPTIMIZATION:
Size: 40 bytes

Field Layout:
ID:        offset= 0, size=8
Balance:   offset= 8, size=8
LastLogin: offset=16, size=8
Age:       offset=24, size=4
Score:     offset=28, size=4
Level:     offset=32, size=2
Active:    offset=34, size=1
Premium:   offset=35, size=1
Verified:  offset=36, size=1

πŸ’° MEMORY SAVINGS:
Before:  56 bytes
After:   40 bytes
Saved:   16 bytes (28.6% reduction)

πŸ“Š For 1 million instances:
Before:  53.41 MB
After:   38.15 MB
Saved:   15.26 MB

5.3 Cache Line Optimization (False Sharing)

False sharing terjadi ketika dua goroutine mengakses field berbeda dalam struct yang sama, tapi field tersebut berada dalam cache line yang sama.

package main

import (
    "fmt"
    "runtime"
    "sync"
    "sync/atomic"
    "time"
    "unsafe"
)

// ❌ BAD - False sharing problem
type CountersBad struct {
    counter1 uint64 // Di cache line yang sama
    counter2 uint64 // Di cache line yang sama
}

// βœ“ GOOD - Separated by cache line padding
type CountersGood struct {
    counter1 uint64
    _        [56]byte // Padding: 64 - 8 = 56 bytes
    counter2 uint64
    _        [56]byte
}

func benchmarkCounters(name string, c1, c2 *uint64) time.Duration {
    const iterations = 10_000_000
    var wg sync.WaitGroup
    
    start := time.Now()
    
    wg.Add(2)
    
    // Goroutine 1: increment counter1
    go func() {
        defer wg.Done()
        for i := 0; i < iterations; i++ {
            atomic.AddUint64(c1, 1)
        }
    }()
    
    // Goroutine 2: increment counter2
    go func() {
        defer wg.Done()
        for i := 0; i < iterations; i++ {
            atomic.AddUint64(c2, 1)
        }
    }()
    
    wg.Wait()
    duration := time.Since(start)
    
    fmt.Printf("%s: %v\n", name, duration)
    return duration
}

func main() {
    runtime.GOMAXPROCS(2) // Use 2 cores for clear effect
    
    fmt.Println("=== FALSE SHARING DEMONSTRATION ===\n")
    fmt.Printf("Cache line size: typically 64 bytes\n")
    fmt.Printf("CountersBad size: %d bytes\n", unsafe.Sizeof(CountersBad{}))
    fmt.Printf("CountersGood size: %d bytes\n\n", unsafe.Sizeof(CountersGood{}))
    
    // Test Bad Layout (with false sharing)
    var bad CountersBad
    timeBad := benchmarkCounters("CountersBad (false sharing)", &bad.counter1, &bad.counter2)
    
    // Test Good Layout (without false sharing)
    var good CountersGood
    timeGood := benchmarkCounters("CountersGood (padded)", &good.counter1, &good.counter2)
    
    improvement := float64(timeBad-timeGood) / float64(timeBad) * 100
    fmt.Printf("\nπŸ’‘ Performance improvement: %.1f%%\n", improvement)
    fmt.Printf("Good layout is %.2fx faster\n", float64(timeBad)/float64(timeGood))
}

5.4 Tool: Automatic Layout Optimizer

package main

import (
    "fmt"
    "sort"
    "unsafe"
)

type FieldDef struct {
    Name      string
    Type      string
    Size      int
    Alignment int
}

type LayoutOptimizer struct {
    fields []FieldDef
}

func NewLayoutOptimizer() *LayoutOptimizer {
    return &LayoutOptimizer{
        fields: make([]FieldDef, 0),
    }
}

func (o *LayoutOptimizer) AddField(name, typeName string, size, alignment int) {
    o.fields = append(o.fields, FieldDef{
        Name:      name,
        Type:      typeName,
        Alignment: alignment,
        Size:      size,
    })
}

func (o *LayoutOptimizer) OptimizeLayout() []FieldDef {
    optimized := make([]FieldDef, len(o.fields))
    copy(optimized, o.fields)
    
    // Sort by alignment (descending), then by size (descending)
    sort.Slice(optimized, func(i, j int) bool {
        if optimized[i].Alignment != optimized[j].Alignment {
            return optimized[i].Alignment > optimized[j].Alignment
        }
        return optimized[i].Size > optimized[j].Size
    })
    
    return optimized
}

func (o *LayoutOptimizer) CalculateSize(fields []FieldDef) int {
    if len(fields) == 0 {
        return 0
    }
    
    // Find maximum alignment
    maxAlign := 0
    for _, f := range fields {
        if f.Alignment > maxAlign {
            maxAlign = f.Alignment
        }
    }
    
    offset := 0
    for _, f := range fields {
        // Add padding for alignment
        padding := (f.Alignment - (offset % f.Alignment)) % f.Alignment
        offset += padding + f.Size
    }
    
    // Add tail padding to make total size multiple of alignment
    tailPadding := (maxAlign - (offset % maxAlign)) % maxAlign
    offset += tailPadding
    
    return offset
}

func (o *LayoutOptimizer) PrintLayout(fields []FieldDef, title string) {
    fmt.Printf("\n=== %s ===\n", title)
    fmt.Println("Field Name          Type        Offset  Size  Align")
    fmt.Println("─────────────────  ──────────  ──────  ────  ─────")
    
    maxAlign := 0
    for _, f := range fields {
        if f.Alignment > maxAlign {
            maxAlign = f.Alignment
        }
    }
    
    offset := 0
    totalPadding := 0
    
    for i, f := range fields {
        // Calculate padding
        padding := (f.Alignment - (offset % f.Alignment)) % f.Alignment
        if padding > 0 {
            fmt.Printf("%-18s  %-10s  %6d  %4d\n", 
                fmt.Sprintf("[padding %d]", i), "", offset, padding)
            totalPadding += padding
        }
        offset += padding
        
        fmt.Printf("%-18s  %-10s  %6d  %4d  %5d\n",
            f.Name, f.Type, offset, f.Size, f.Alignment)
        offset += f.Size
    }
    
    // Tail padding
    tailPadding := (maxAlign - (offset % maxAlign)) % maxAlign
    if tailPadding > 0 {
        fmt.Printf("%-18s  %-10s  %6d  %4d\n", 
            "[tail padding]", "", offset, tailPadding)
        totalPadding += tailPadding
    }
    offset += tailPadding
    
    fmt.Println("─────────────────  ──────────  ──────  ────  ─────")
    fmt.Printf("Total Size: %d bytes (padding: %d bytes, %.1f%%)\n", 
        offset, totalPadding, float64(totalPadding)/float64(offset)*100)
}

func main() {
    // Example: Optimize a User struct
    optimizer := NewLayoutOptimizer()
    
    // Add fields in random order (as programmer might write)
    optimizer.AddField("Active", "bool", 1, 1)
    optimizer.AddField("Name", "string", 16, 8)
    optimizer.AddField("Age", "int32", 4, 4)
    optimizer.AddField("ID", "int64", 8, 8)
    optimizer.AddField("Premium", "bool", 1, 1)
    optimizer.AddField("Score", "float32", 4, 4)
    optimizer.AddField("Level", "int16", 2, 2)
    optimizer.AddField("Balance", "float64", 8, 8)
    
    // Show original layout
    originalSize := optimizer.CalculateSize(optimizer.fields)
    optimizer.PrintLayout(optimizer.fields, "ORIGINAL LAYOUT")
    
    // Show optimized layout
    optimized := optimizer.OptimizeLayout()
    optimizedSize := optimizer.CalculateSize(optimized)
    optimizer.PrintLayout(optimized, "OPTIMIZED LAYOUT")
    
    // Show comparison
    savings := originalSize - optimizedSize
    savingsPct := float64(savings) / float64(originalSize) * 100
    
    fmt.Printf("\nπŸ’° OPTIMIZATION RESULT:\n")
    fmt.Printf("Original:  %d bytes\n", originalSize)
    fmt.Printf("Optimized: %d bytes\n", optimizedSize)
    fmt.Printf("Saved:     %d bytes (%.1f%% reduction)\n", savings, savingsPct)
}

6. Advanced Topics

6.1 Embedded Structs

package main

import (
    "fmt"
    "unsafe"
)

type Base struct {
    ID   int64
    Name string
}

type Extended struct {
    Base          // Embedded struct
    Age  int32
    Tags []string
}

// Dengan explicit field
type ExtendedExplicit struct {
    base Base    // Named field
    Age  int32
    Tags []string
}

func main() {
    var e1 Extended
    var e2 ExtendedExplicit
    
    fmt.Println("=== EMBEDDED STRUCT LAYOUT ===\n")
    
    fmt.Println("Extended (embedded):")
    fmt.Printf("Size: %d bytes\n", unsafe.Sizeof(e1))
    fmt.Printf("Base.ID offset:   %d\n", unsafe.Offsetof(e1.ID))
    fmt.Printf("Base.Name offset: %d\n", unsafe.Offsetof(e1.Name))
    fmt.Printf("Age offset:       %d\n", unsafe.Offsetof(e1.Age))
    fmt.Printf("Tags offset:      %d\n", unsafe.Offsetof(e1.Tags))
    
    fmt.Println("\nExtendedExplicit (named field):")
    fmt.Printf("Size: %d bytes\n", unsafe.Sizeof(e2))
    fmt.Printf("base offset:      %d\n", unsafe.Offsetof(e2.base))
    fmt.Printf("Age offset:       %d\n", unsafe.Offsetof(e2.Age))
    fmt.Printf("Tags offset:      %d\n", unsafe.Offsetof(e2.Tags))
    
    // Layout identik!
    fmt.Printf("\nβœ“ Both have identical memory layout\n")
}

6.2 Interface Values

package main

import (
    "fmt"
    "unsafe"
)

type MyInterface interface {
    Method()
}

type MyStruct struct {
    Value int64
}

func (m MyStruct) Method() {}

func main() {
    var i MyInterface
    var s MyStruct
    
    fmt.Println("=== INTERFACE VALUE LAYOUT ===\n")
    fmt.Printf("Interface size: %d bytes\n", unsafe.Sizeof(i))
    fmt.Printf("Struct size:    %d bytes\n", unsafe.Sizeof(s))
    
    // Interface value berisi 2 pointer:
    // 1. Type information (itab)
    // 2. Data pointer
    fmt.Println("\nInterface contains:")
    fmt.Println("- Type pointer (8 bytes)")
    fmt.Println("- Data pointer (8 bytes)")
    fmt.Println("Total: 16 bytes on 64-bit")
    
    i = s
    fmt.Printf("\nInterface after assignment: %d bytes\n", unsafe.Sizeof(i))
}

6.3 Slice dan String Internals

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    fmt.Println("=== SLICE INTERNAL LAYOUT ===\n")
    
    slice := []int{1, 2, 3, 4, 5}
    
    // Slice header: pointer, len, cap
    type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
    }
    
    header := (*SliceHeader)(unsafe.Pointer(&slice))
    
    fmt.Printf("Slice size: %d bytes\n", unsafe.Sizeof(slice))
    fmt.Println("\nSlice header contains:")
    fmt.Printf("- Data pointer: %d bytes (address: %x)\n", 
        unsafe.Sizeof(header.Data), header.Data)
    fmt.Printf("- Length:       %d bytes (value: %d)\n", 
        unsafe.Sizeof(header.Len), header.Len)
    fmt.Printf("- Capacity:     %d bytes (value: %d)\n", 
        unsafe.Sizeof(header.Cap), header.Cap)
    fmt.Printf("Total: %d bytes\n", unsafe.Sizeof(slice))
    
    fmt.Println("\n=== STRING INTERNAL LAYOUT ===\n")
    
    str := "Hello, Go!"
    
    // String header: pointer, len
    type StringHeader struct {
        Data uintptr
        Len  int
    }
    
    strHeader := (*StringHeader)(unsafe.Pointer(&str))
    
    fmt.Printf("String size: %d bytes\n", unsafe.Sizeof(str))
    fmt.Println("\nString header contains:")
    fmt.Printf("- Data pointer: %d bytes (address: %x)\n", 
        unsafe.Sizeof(strHeader.Data), strHeader.Data)
    fmt.Printf("- Length:       %d bytes (value: %d)\n", 
        unsafe.Sizeof(strHeader.Len), strHeader.Len)
    fmt.Printf("Total: %d bytes\n", unsafe.Sizeof(str))
    
    fmt.Println("\nπŸ’‘ Note: The header is constant size,")
    fmt.Println("   but the actual data is stored elsewhere")
}

6.4 Map Internals (Simplified)

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    m := make(map[string]int)
    
    fmt.Println("=== MAP SIZE ===\n")
    fmt.Printf("Map variable size: %d bytes\n", unsafe.Sizeof(m))
    fmt.Println("\nπŸ’‘ Maps are references to runtime structures")
    fmt.Println("   The actual map data is allocated on the heap")
    
    // Add some data
    m["one"] = 1
    m["two"] = 2
    
    fmt.Printf("\nMap with data still has same variable size: %d bytes\n", 
        unsafe.Sizeof(m))
}

6.5 Channel Internals

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    ch := make(chan int, 10)
    
    fmt.Println("=== CHANNEL SIZE ===\n")
    fmt.Printf("Channel variable size: %d bytes\n", unsafe.Sizeof(ch))
    fmt.Println("\nπŸ’‘ Channels are pointers to runtime structures")
    fmt.Println("   Buffer capacity doesn't affect variable size")
    
    ch2 := make(chan int, 1000)
    fmt.Printf("\nChannel with large buffer: %d bytes (same)\n", 
        unsafe.Sizeof(ch2))
}

7. Best Practices

7.1 Checklist untuk Memory Layout

βœ… DO:

  1. Urutkan field dari alignment terbesar ke terkecil

    type Good struct {
        ptr  *int    // 8 bytes
        val  int64   // 8 bytes
        flag int32   // 4 bytes
        b    bool    // 1 byte
    }
  2. Kelompokkan field yang sering diakses bersama

    type CacheFriendly struct {
        // Frequently accessed together
        x, y, z float64
        
        // Less frequently accessed
        metadata string
        extra    []byte
    }
  3. Gunakan atomic types untuk concurrent counters

    import "sync/atomic"
    
    type Stats struct {
        requests atomic.Uint64
        errors   atomic.Uint64
    }
  4. Pertimbangkan cache line size untuk concurrent access

    type ThreadSafeCounters struct {
        counter1 uint64
        _        [56]byte // Cache line padding
        counter2 uint64
        _        [56]byte
    }

❌ DON'T:

  1. Jangan letakkan field kecil di antara field besar

    type Bad struct {
        a int64  // 8 bytes
        b bool   // 1 byte + 7 padding
        c int64  // 8 bytes
        // Total: 24 bytes dengan banyak padding
    }
  2. Jangan gunakan 64-bit atomic pada field yang bukan pertama (32-bit)

    type Unsafe32bit struct {
        id    int32
        count uint64 // UNSAFE untuk atomic pada 32-bit!
    }
  3. Jangan abaikan padding saat menghitung ukuran struct

    // WRONG assumption:
    // type MyStruct has 3 fields: int64(8) + int32(4) + bool(1) = 13 bytes
    // ACTUAL: 24 bytes due to padding!

7.2 Performance Tips

Tip 1: Gunakan Profiling Tools

package main

import (
    "fmt"
    "runtime"
    "unsafe"
)

type MyStruct struct {
    data [1000]byte
}

func analyzeMemory() {
    var m runtime.MemStats
    
    // Sebelum alokasi
    runtime.GC()
    runtime.ReadMemStats(&m)
    before := m.Alloc
    
    // Alokasi
    const count = 10000
    slice := make([]MyStruct, count)
    
    // Setelah alokasi
    runtime.ReadMemStats(&m)
    after := m.Alloc
    
    allocated := after - before
    perStruct := allocated / count
    actualSize := unsafe.Sizeof(MyStruct{})
    
    fmt.Printf("Allocated: %d bytes\n", allocated)
    fmt.Printf("Per struct: %d bytes\n", perStruct)
    fmt.Printf("Actual sizeof: %d bytes\n", actualSize)
    
    _ = slice // Prevent optimization
}

func main() {
    analyzeMemory()
}

Tip 2: Benchmark Different Layouts

package main

import (
    "testing"
)

type LayoutA struct {
    a bool
    b int64
    c bool
}

type LayoutB struct {
    b int64
    a bool
    c bool
}

func BenchmarkLayoutA(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = LayoutA{a: true, b: 123, c: false}
    }
}

func BenchmarkLayoutB(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = LayoutB{b: 123, a: true, c: false}
    }
}

// Run: go test -bench=. -benchmem

7.3 Common Patterns

Pattern 1: Hot/Cold Fields

package main

// Pisahkan field yang sering diakses (hot) dari yang jarang (cold)
type User struct {
    // Hot fields - sering diakses
    ID        int64
    Name      string
    Email     string
    Active    bool
    
    // Cold fields - jarang diakses
    CreatedAt    int64
    UpdatedAt    int64
    LastLoginIP  string
    Preferences  []byte
}

Pattern 2: Embedding untuk Reusability

package main

import "sync/atomic"

// Base counter yang bisa di-embed
type AtomicCounter struct {
    _ [0]atomic.Uint64 // Force alignment
    value uint64
}

func (c *AtomicCounter) Inc() uint64 {
    return atomic.AddUint64(&c.value, 1)
}

func (c *AtomicCounter) Get() uint64 {
    return atomic.LoadUint64(&c.value)
}

// Gunakan di berbagai struct
type RequestStats struct {
    AtomicCounter // Embedded, selalu safe
    endpoint string
}

type UserStats struct {
    name string
    AtomicCounter // Embedded, selalu safe
}

Pattern 3: Memory Pool dengan Optimal Layout

package main

import (
    "sync"
)

type OptimizedObject struct {
    // Optimized layout
    ptr  *int
    val  int64
    flag int32
    b    bool
}

var objectPool = sync.Pool{
    New: func() interface{} {
        return &OptimizedObject{}
    },
}

func GetObject() *OptimizedObject {
    return objectPool.Get().(*OptimizedObject)
}

func PutObject(obj *OptimizedObject) {
    // Reset
    *obj = OptimizedObject{}
    objectPool.Put(obj)
}

func main() {
    obj := GetObject()
    defer PutObject(obj)
    
    // Use obj...
}

7.4 Debugging Tools

Tool 1: Struct Layout Visualizer

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func VisualizeStruct(v interface{}) {
    t := reflect.TypeOf(v)
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
    }
    
    fmt.Printf("\n=== STRUCT: %s ===\n", t.Name())
    fmt.Printf("Total Size: %d bytes\n", t.Size())
    fmt.Printf("Alignment:  %d bytes\n\n", t.Align())
    
    // Print visual representation
    fmt.Println("Memory Layout Visualization:")
    fmt.Println("β”Œ" + repeat("─", 80) + "┐")
    
    offset := uintptr(0)
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        
        // Check for padding before this field
        if field.Offset > offset {
            padding := field.Offset - offset
            fmt.Printf("β”‚ [PADDING: %d bytes at offset %d]%sβ”‚\n",
                padding, offset, repeat(" ", 80-int(len(fmt.Sprintf(" [PADDING: %d bytes at offset %d]", padding, offset)))))
        }
        
        // Print field
        fieldInfo := fmt.Sprintf(" %s (%s): %d bytes at offset %d",
            field.Name, field.Type.Name(), field.Type.Size(), field.Offset)
        fmt.Printf("β”‚%s%sβ”‚\n", fieldInfo, repeat(" ", 80-len(fieldInfo)))
        
        offset = field.Offset + field.Type.Size()
    }
    
    // Check for tail padding
    if t.Size() > offset {
        padding := t.Size() - offset
        fmt.Printf("β”‚ [TAIL PADDING: %d bytes]%sβ”‚\n",
            padding, repeat(" ", 80-int(len(fmt.Sprintf(" [TAIL PADDING: %d bytes]", padding)))))
    }
    
    fmt.Println("β””" + repeat("─", 80) + "β”˜")
}

func repeat(s string, count int) string {
    result := ""
    for i := 0; i < count; i++ {
        result += s
    }
    return result
}

// Example usage
type ExampleStruct struct {
    A bool
    B int64
    C int16
    D bool
}

func main() {
    VisualizeStruct(ExampleStruct{})
}

Tool 2: Memory Layout Comparator

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

type LayoutComparator struct {
    structs []interface{}
    names   []string
}

func NewLayoutComparator() *LayoutComparator {
    return &LayoutComparator{
        structs: make([]interface{}, 0),
        names:   make([]string, 0),
    }
}

func (lc *LayoutComparator) Add(name string, s interface{}) {
    lc.names = append(lc.names, name)
    lc.structs = append(lc.structs, s)
}

func (lc *LayoutComparator) Compare() {
    fmt.Println("\n=== LAYOUT COMPARISON ===\n")
    
    // Print header
    fmt.Printf("%-20s | %10s | %10s | %10s\n", "Struct", "Size", "Alignment", "Fields")
    fmt.Println(repeat("-", 60))
    
    // Print each struct
    for i, s := range lc.structs {
        t := reflect.TypeOf(s)
        if t.Kind() == reflect.Ptr {
            t = t.Elem()
        }
        
        fmt.Printf("%-20s | %10d | %10d | %10d\n",
            lc.names[i], t.Size(), t.Align(), t.NumField())
    }
    
    // Find best and worst
    var bestIdx, worstIdx int
    bestSize := reflect.TypeOf(lc.structs[0]).Size()
    worstSize := bestSize
    
    for i, s := range lc.structs[1:] {
        size := reflect.TypeOf(s).Size()
        if size < bestSize {
            bestSize = size
            bestIdx = i + 1
        }
        if size > worstSize {
            worstSize = size
            worstIdx = i + 1
        }
    }
    
    fmt.Printf("\nπŸ† Best (smallest):  %s (%d bytes)\n", lc.names[bestIdx], bestSize)
    fmt.Printf("⚠️  Worst (largest): %s (%d bytes)\n", lc.names[worstIdx], worstSize)
    
    if bestIdx != worstIdx {
        savings := worstSize - bestSize
        pct := float64(savings) / float64(worstSize) * 100
        fmt.Printf("πŸ’° Potential savings: %d bytes (%.1f%%)\n", savings, pct)
    }
}

func repeat(s string, count int) string {
    result := ""
    for i := 0; i < count; i++ {
        result += s
    }
    return result
}

// Example: Compare different layouts
type Layout1 struct {
    a bool
    b int64
    c bool
}

type Layout2 struct {
    b int64
    a bool
    c bool
}

type Layout3 struct {
    b int64
    c bool
    a bool
}

func main() {
    comp := NewLayoutComparator()
    comp.Add("Layout1 (bad)", Layout1{})
    comp.Add("Layout2 (good)", Layout2{})
    comp.Add("Layout3 (good)", Layout3{})
    comp.Compare()
}

7.5 Production Checklist

Sebelum deploy ke production, periksa:

  • [ ] Struct besar sudah dioptimasi layout-nya

  • [ ] Field yang sering diakses concurrent sudah menggunakan atomic types

  • [ ] Tidak ada 64-bit atomic operation pada field non-pertama (untuk support 32-bit)

  • [ ] Cache line padding ditambahkan untuk high-contention concurrent access

  • [ ] Profiling sudah dilakukan untuk memastikan tidak ada memory bloat

  • [ ] Documentation ditambahkan untuk layout yang critical


8. Real-World Examples

8.1 HTTP Server Stats

package main

import (
    "fmt"
    "sync/atomic"
    "time"
)

// ❌ BEFORE: 72 bytes with poor layout
type ServerStatsBefore struct {
    Requests       uint64
    RequestsActive int32
    Errors         uint64
    BytesSent      uint64
    StartTime      time.Time // 24 bytes
    LastRequest    time.Time // 24 bytes
}

// βœ“ AFTER: 56 bytes with optimized layout
type ServerStatsAfter struct {
    // Atomic counters - always safe
    Requests  atomic.Uint64
    Errors    atomic.Uint64
    BytesSent atomic.Uint64
    
    // Time values - 24 bytes each
    StartTime   time.Time
    
    // Smaller fields
    ActiveReqs  atomic.Int32
    _           [4]byte // Explicit padding for clarity
}

func (s *ServerStatsAfter) RecordRequest(bytes uint64, err bool) {
    s.Requests.Add(1)
    s.BytesSent.Add(bytes)
    if err {
        s.Errors.Add(1)
    }
}

func (s *ServerStatsAfter) ActiveIncrement() {
    s.ActiveReqs.Add(1)
}

func (s *ServerStatsAfter) ActiveDecrement() {
    s.ActiveReqs.Add(-1)
}

func (s *ServerStatsAfter) Report() {
    fmt.Printf("Requests: %d, Errors: %d, Active: %d, Bytes: %d\n",
        s.Requests.Load(),
        s.Errors.Load(),
        s.ActiveReqs.Load(),
        s.BytesSent.Load())
}

func main() {
    stats := &ServerStatsAfter{
        StartTime: time.Now(),
    }
    
    // Simulate requests
    for i := 0; i < 1000; i++ {
        stats.RecordRequest(1024, i%100 == 0)
    }
    
    stats.Report()
}

8.2 Cache Entry

package main

import (
    "sync"
    "sync/atomic"
    "time"
)

// Optimized cache entry with minimal padding
type CacheEntry struct {
    // Most frequently accessed (hot path)
    value     atomic.Value  // 16 bytes (interface)
    hits      atomic.Uint64 // 8 bytes
    
    // Frequently accessed
    expireAt  atomic.Int64  // 8 bytes (unix timestamp)
    
    // Less frequently accessed (cold path)
    key       string        // 16 bytes
    createdAt time.Time     // 24 bytes
    
    // Rarely accessed
    mu        sync.RWMutex  // 24 bytes
    metadata  map[string]string
}

func NewCacheEntry(key string, value interface{}, ttl time.Duration) *CacheEntry {
    entry := &CacheEntry{
        key:       key,
        createdAt: time.Now(),
    }
    entry.value.Store(value)
    entry.expireAt.Store(time.Now().Add(ttl).Unix())
    return entry
}

func (e *CacheEntry) Get() (interface{}, bool) {
    // Check expiration without lock
    if time.Now().Unix() > e.expireAt.Load() {
        return nil, false
    }
    
    e.hits.Add(1)
    return e.value.Load(), true
}

func (e *CacheEntry) Update(value interface{}, ttl time.Duration) {
    e.value.Store(value)
    e.expireAt.Store(time.Now().Add(ttl).Unix())
}

func (e *CacheEntry) IsExpired() bool {
    return time.Now().Unix() > e.expireAt.Load()
}

func (e *CacheEntry) Hits() uint64 {
    return e.hits.Load()
}

8.3 Ring Buffer

package main

import (
    "fmt"
    "sync/atomic"
)

// Lock-free ring buffer dengan optimized layout
type RingBuffer struct {
    // Cache line 1: write-related (avoid false sharing)
    writePos atomic.Uint64
    _        [56]byte // Padding
    
    // Cache line 2: read-related
    readPos  atomic.Uint64
    _        [56]byte // Padding
    
    // Cache line 3+: configuration (read-only after init)
    buffer   []interface{}
    capacity uint64
    mask     uint64
}

func NewRingBuffer(capacity uint64) *RingBuffer {
    // Ensure capacity is power of 2
    if capacity&(capacity-1) != 0 {
        panic("capacity must be power of 2")
    }
    
    return &RingBuffer{
        buffer:   make([]interface{}, capacity),
        capacity: capacity,
        mask:     capacity - 1,
    }
}

func (rb *RingBuffer) Push(item interface{}) bool {
    for {
        writePos := rb.writePos.Load()
        readPos := rb.readPos.Load()
        
        // Check if full
        if writePos-readPos >= rb.capacity {
            return false
        }
        
        // Try to reserve slot
        if rb.writePos.CompareAndSwap(writePos, writePos+1) {
            rb.buffer[writePos&rb.mask] = item
            return true
        }
    }
}

func (rb *RingBuffer) Pop() (interface{}, bool) {
    for {
        readPos := rb.readPos.Load()
        writePos := rb.writePos.Load()
        
        // Check if empty
        if readPos >= writePos {
            return nil, false
        }
        
        // Try to reserve slot
        if rb.readPos.CompareAndSwap(readPos, readPos+1) {
            item := rb.buffer[readPos&rb.mask]
            rb.buffer[readPos&rb.mask] = nil // Clear reference
            return item, true
        }
    }
}

func (rb *RingBuffer) Len() uint64 {
    return rb.writePos.Load() - rb.readPos.Load()
}

func main() {
    rb := NewRingBuffer(8)
    
    // Push items
    for i := 0; i < 5; i++ {
        rb.Push(fmt.Sprintf("item-%d", i))
    }
    
    fmt.Printf("Length: %d\n", rb.Len())
    
    // Pop items
    for {
        item, ok := rb.Pop()
        if !ok {
            break
        }
        fmt.Printf("Popped: %v\n", item)
    }
}

8.4 Metrics Collector

package main

import (
    "fmt"
    "sync/atomic"
    "time"
)

// Optimized metrics collector untuk high-throughput systems
type MetricsCollector struct {
    // Hot counters - frequently updated
    totalRequests atomic.Uint64
    _             [56]byte
    
    totalErrors   atomic.Uint64
    _             [56]byte
    
    totalBytes    atomic.Uint64
    _             [56]byte
    
    // Histogram buckets (read less frequently)
    latencyBuckets [10]atomic.Uint64
    
    // Configuration (read-only)
    startTime time.Time
    name      string
}

func NewMetricsCollector(name string) *MetricsCollector {
    return &MetricsCollector{
        startTime: time.Now(),
        name:      name,
    }
}

func (m *MetricsCollector) RecordRequest(latencyMs int, bytes uint64, hasError bool) {
    m.totalRequests.Add(1)
    m.totalBytes.Add(bytes)
    
    if hasError {
        m.totalErrors.Add(1)
    }
    
    // Record latency in appropriate bucket
    bucketIdx := latencyMs / 100 // 100ms buckets
    if bucketIdx >= len(m.latencyBuckets) {
        bucketIdx = len(m.latencyBuckets) - 1
    }
    m.latencyBuckets[bucketIdx].Add(1)
}

func (m *MetricsCollector) Snapshot() map[string]interface{} {
    snapshot := make(map[string]interface{})
    
    snapshot["name"] = m.name
    snapshot["uptime"] = time.Since(m.startTime).String()
    snapshot["total_requests"] = m.totalRequests.Load()
    snapshot["total_errors"] = m.totalErrors.Load()
    snapshot["total_bytes"] = m.totalBytes.Load()
    
    // Latency distribution
    latency := make([]uint64, len(m.latencyBuckets))
    for i := range m.latencyBuckets {
        latency[i] = m.latencyBuckets[i].Load()
    }
    snapshot["latency_distribution"] = latency
    
    return snapshot
}

func (m *MetricsCollector) Report() {
    snapshot := m.Snapshot()
    fmt.Printf("\n=== Metrics: %s ===\n", snapshot["name"])
    fmt.Printf("Uptime:        %s\n", snapshot["uptime"])
    fmt.Printf("Requests:      %d\n", snapshot["total_requests"])
    fmt.Printf("Errors:        %d\n", snapshot["total_errors"])
    fmt.Printf("Bytes:         %d\n", snapshot["total_bytes"])
    
    latency := snapshot["latency_distribution"].([]uint64)
    fmt.Println("\nLatency Distribution:")
    for i, count := range latency {
        if count > 0 {
            fmt.Printf("  %d-%dms: %d\n", i*100, (i+1)*100, count)
        }
    }
}

func main() {
    collector := NewMetricsCollector("api-server")
    
    // Simulate traffic
    for i := 0; i < 10000; i++ {
        latency := (i % 10) * 50 // 0-450ms
        bytes := uint64(100 + (i % 1000))
        hasError := i%100 == 0
        
        collector.RecordRequest(latency, bytes, hasError)
    }
    
    collector.Report()
}

9. Summary & Quick Reference

9.1 Alignment Rules Quick Reference

Type
Size
Alignment (64-bit)
Alignment (32-bit)

bool, int8, uint8

1

1

1

int16, uint16

2

2

2

int32, uint32, float32

4

4

4

int64, uint64, float64

8

8

4

complex64

8

8

4

complex128

16

8

4

pointer, uintptr

8/4

8

4

string

16/8

8

4

slice

24/12

8

4

map, channel, func

8/4

8

4

9.2 Optimization Workflow

1. Identify large or frequently-allocated structs
   ↓
2. Analyze current layout (use unsafe.Sizeof, Offsetof, Alignof)
   ↓
3. Sort fields by alignment (largest first)
   ↓
4. Group fields accessed together (cache locality)
   ↓
5. Add cache line padding for concurrent access (if needed)
   ↓
6. Verify with benchmarks
   ↓
7. Document the layout decision

9.3 Key Takeaways

  1. Struct size β‰  sum of field sizes - Padding adds overhead

  2. Field order matters - Can save 30-50% memory

  3. 64-bit atomic requires 8-byte alignment - Critical on 32-bit

  4. Cache lines are 64 bytes - Prevent false sharing

  5. Use atomic types - Safer than manual atomic operations

  6. Profile first - Don't optimize prematurely

  7. Document layout decisions - Help future maintainers

9.4 Common Mistakes to Avoid

// ❌ DON'T: Mix small and large fields randomly
type Bad struct {
    a bool   // 1 byte + 7 padding
    b int64  // 8 bytes
    c bool   // 1 byte + 7 padding
    d int64  // 8 bytes
} // 32 bytes

// βœ“ DO: Group by size
type Good struct {
    b int64  // 8 bytes
    d int64  // 8 bytes
    a bool   // 1 byte
    c bool   // 1 byte
    // 6 bytes padding
} // 24 bytes - 25% smaller!

9.5 Useful Functions Reference

import "unsafe"

// Get size of a type
size := unsafe.Sizeof(variable)

// Get alignment of a type
align := unsafe.Alignof(variable)

// Get offset of struct field
offset := unsafe.Offsetof(structVar.field)

// Using reflect (slower but safer)
import "reflect"

t := reflect.TypeOf(variable)
size := t.Size()
align := t.Align()
fieldAlign := t.FieldAlign()

10. Further Resources

Official Documentation

Tools

  • go tool compile -S - View assembly to understand memory layout

  • go test -bench -benchmem - Measure memory allocations

  • pprof - Profile memory usage in production

Best Practices

  • Profile before optimizing

  • Document non-obvious layout decisions

  • Use code comments to explain padding

  • Test on both 32-bit and 64-bit if supporting both

  • Consider using go vet for atomic alignment issues (Go 1.22+)


Appendix: Complete Working Examples

Example A: Layout Analyzer Tool

package main

import (
    "fmt"
    "reflect"
    "strings"
    "unsafe"
)

type LayoutAnalyzer struct {
    typeName string
    size     uintptr
    align    uintptr
    fields   []fieldInfo
}

type fieldInfo struct {
    name   string
    typ    string
    offset uintptr
    size   uintptr
    align  uintptr
}

func AnalyzeStruct(v interface{}) *LayoutAnalyzer {
    t := reflect.TypeOf(v)
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
    }
    
    analyzer := &LayoutAnalyzer{
        typeName: t.Name(),
        size:     t.Size(),
        align:    uintptr(t.Align()),
        fields:   make([]fieldInfo, 0, t.NumField()),
    }
    
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        analyzer.fields = append(analyzer.fields, fieldInfo{
            name:   field.Name,
            typ:    field.Type.String(),
            offset: field.Offset,
            size:   field.Type.Size(),
            align:  uintptr(field.Type.Align()),
        })
    }
    
    return analyzer
}

func (la *LayoutAnalyzer) Print() {
    fmt.Printf("\n╔══════════════════════════════════════════════════════════════╗\n")
    fmt.Printf("β•‘ Struct: %-50s β•‘\n", la.typeName)
    fmt.Printf("β•‘ Size: %d bytes  β”‚  Alignment: %d bytes%-20sβ•‘\n", 
        la.size, la.align, "")
    fmt.Printf("╠══════════════════════════════════════════════════════════════╣\n")
    
    actualSize := uintptr(0)
    offset := uintptr(0)
    
    for _, field := range la.fields {
        // Padding before field
        if field.offset > offset {
            padding := field.offset - offset
            fmt.Printf("β•‘ %s %d bytes)\n",
                padRight("[PADDING]", 50), padding)
        }
        
        // Field info
        fieldStr := fmt.Sprintf("%s (%s)", field.name, field.typ)
        fmt.Printf("β•‘ %-30s offset:%-5d size:%-4d align:%-4d β•‘\n",
            fieldStr, field.offset, field.size, field.align)
        
        actualSize += field.size
        offset = field.offset + field.size
    }
    
    // Tail padding
    if la.size > offset {
        padding := la.size - offset
        fmt.Printf("β•‘ %s %d bytes)\n",
            padRight("[TAIL PADDING]", 40), padding)
    }
    
    fmt.Printf("╠══════════════════════════════════════════════════════════════╣\n")
    
    totalPadding := la.size - actualSize
    paddingPct := float64(totalPadding) / float64(la.size) * 100
    
    fmt.Printf("β•‘ Field Data: %d bytes  β”‚  Padding: %d bytes (%.1f%%)%-10sβ•‘\n",
        actualSize, totalPadding, paddingPct, "")
    fmt.Printf("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n")
}

func padRight(s string, length int) string {
    if len(s) >= length {
        return s
    }
    return s + strings.Repeat(" ", length-len(s))
}

// Test with various structs
type TestStruct1 struct {
    A bool
    B int64
    C int16
    D bool
}

type TestStruct2 struct {
    B int64
    C int16
    A bool
    D bool
}

func main() {
    fmt.Println("Memory Layout Analysis Tool")
    fmt.Println(strings.Repeat("=", 64))
    
    analyzer1 := AnalyzeStruct(TestStruct1{})
    analyzer1.Print()
    
    analyzer2 := AnalyzeStruct(TestStruct2{})
    analyzer2.Print()
    
    fmt.Printf("\nπŸ’‘ Optimization: Struct2 saves %d bytes (%.1f%% reduction)\n",
        analyzer1.size-analyzer2.size,
        float64(analyzer1.size-analyzer2.size)/float64(analyzer1.size)*100)
}

Kesimpulan:

Memory layout adalah aspek fundamental dalam pemrograman Go yang sering diabaikan. Dengan memahami alignment, padding, dan optimisasi layout, Anda dapat:

  • Mengurangi penggunaan memori hingga 30-50%

  • Meningkatkan performa cache CPU

  • Menulis concurrent code yang aman dan efisien

  • Menghindari subtle bugs pada atomic operations

Ingat: Profile first, optimize second. Gunakan pengetahuan ini ketika profiling menunjukkan memory atau performance bottleneck.

Last updated