When writing Go code, it’s easy to forget what’s happening under the hood—especially when it comes to memory layout. But did you know that how you organize fields in a struct
can actually bloat memory and even affect performance?
Let’s take a fun but technical dive into how memory alignment works in Go, and why struct layout matters more than you might think.
🧠 What Is Memory Alignment, Exactly?
Memory alignment is a concept rooted in how CPUs access memory. Most modern CPUs are optimized to access memory at aligned addresses—that is, addresses that are multiples of the data’s size.
For example:
- An
int64
(8 bytes) should ideally start at a memory address that’s a multiple of 8. - An
int32
(4 bytes) should start at a multiple of 4.
If a variable is misaligned, the CPU might need to perform multiple memory reads just to get the full data. This slows things down. On top of that, if a variable spans across two cache lines, you’ll suffer a performance penalty because the CPU has to load both cache lines.
Here’s a simple analogy: imagine reading a sentence split across two pages in a book. You flip once, then again, just to get the whole message. Alignment keeps your "sentence" on the same page.
TL;DR:
- Aligned data = fast memory access
- Misaligned data = slow, possibly multiple reads
🛠️ How Go Handles Memory Alignment
Go takes care of alignment automatically. Each data type has an alignment requirement, and Go inserts padding bytes between struct fields to ensure proper alignment.
Let’s look at this struct:
type PoorlyAligned struct {
a byte // 1 byte
b int32 // 4 bytes
c int64 // 8 bytes
}
Although the fields themselves total 13 bytes, the compiler inserts padding to align each field properly. This results in:
📆 Total size: 24 bytes
Why?
Field | Offset | Size | Notes |
---|---|---|---|
a (byte) | 0 | 1 | |
padding | 1–3 | 3 | To align int32 on 4-byte boundary |
b (int32) | 4–7 | 4 | |
padding | 8–11 | 4 | To align int64 on 8-byte boundary |
c (int64) | 12–19 | 8 | Misaligned if not padded |
padding | 20–23 | 4 | To round struct size to 8-byte multiple |
✅ Well-Aligned Layout = Happy Memory
Now let’s rearrange the fields:
type WellAligned struct {
c int64 // 8 bytes
b int32 // 4 bytes
a byte // 1 byte
}
Result:
📆 Total size: 16 bytes
Field | Offset | Size |
---|---|---|
c (int64) | 0–7 | 8 |
b (int32) | 8–11 | 4 |
a (byte) | 12 | 1 |
padding | 13–15 | 3 |
💡 That’s a 33% size reduction—just by reordering the fields.
🚀 Real-World Impact: Memory and Performance
Why does this matter?
- Less memory per struct = lower overall memory usage
- Smaller structs fit better into CPU cache lines
- Better cache usage = fewer cache misses = faster processing
- Less memory used = less work for the garbage collector
🔬 Benchmarking Time!
Let’s benchmark two slices: one using the poorly-aligned struct, one using the optimized version.
package main
import (
"testing"
)
type PoorlyAligned struct {
a byte
b int32
c int64
}
type WellAligned struct {
c int64
b int32
a byte
}
var poorlySlice = make([]PoorlyAligned, 1_000_000)
var wellSlice = make([]WellAligned, 1_000_000)
func BenchmarkPoorlyAligned(b *testing.B) {
var sum int64
for n := 0; n < b.N; n++ {
for i := range poorlySlice {
sum += poorlySlice[i].c
}
}
}
func BenchmarkWellAligned(b *testing.B) {
var sum int64
for n := 0; n < b.N; n++ {
for i := range wellSlice {
sum += wellSlice[i].c
}
}
}
📊 Typical Results:
goos: darwin
goarch: arm64
pkg: metaleap/pkg/tunatest
cpu: Apple M1
BenchmarkPoorlyAligned-8 3609 323200 ns/op
BenchmarkWellAligned-8 3759 316617 ns/op
PASS
✅ Result: On my Apple M1 chip, optimizing the struct layout resulted in a ~2% performance improvement.
🛠️ Tools: Check Alignment with go vet
You don’t need to do this manually. Go provides a vetting tool to help:
go vet -fieldalignment ./...
It will suggest better ordering for your structs when applicable, like:
struct with 24 bytes could be 16 bytes
✅ Best Practices for Struct Layout in Go
- Order fields from largest to smallest alignment.
- Group fields with the same size together.
- Consider memory layout when defining high-volume or performance-critical structs.
- Use
go vet -fieldalignment
for automatic suggestions.
📝 Final Thoughts
Memory alignment is one of those “under-the-hood” details that can have outsized effects in real-world programs—especially those dealing with millions of objects or high-performance data processing.
With just a bit of attention to field ordering, you can:
- Save memory
- Speed up your programs
- Make your data more cache-friendly
Go’s compiler does the heavy lifting to ensure safety and correctness. Your job is to be mindful of layout when performance or memory use matters.
Top comments (1)
Nice post, thanks Tu 🎉
Some comments may only be visible to logged-in visitors. Sign in to view all comments.