cd ~/Angga

Unlocking Go Performance: The Art of Struct Field Ordering

January 14, 2025

Go

Performance

Optimization

When shipping performance-critical services in Go, every optimization matters. During my internship, I discovered one surprising area for improvement: the order of fields in structs. My senior engineer suggested reordering fields in my PR, and while the manual approach seemed overwhelming at first, I soon found there's an easier way to unlock these hidden performance gains.

Why Field Ordering Matters

To understand the impact, let's first examine two nearly identical structs with a simple memory comparison:

type (
	Bad struct {
		IsActive  bool
		TotalTime int64
		AvgScore  float32
	}
	Good struct {
		TotalTime int64
		AvgScore  float32
		IsActive  bool
	}
)
 
func main() {
	fmt.Println(unsafe.Sizeof(Bad{}))  // Output: 24
	fmt.Println(unsafe.Sizeof(Good{})) // Output: 16
}

Despite containing identical fields, the Bad struct consumes 24 bytes while the Good struct uses only 16 bytes - a 33% reduction! This difference stems from how data is arranged in memory through a concept called data structure alignment.

Understanding Data Alignment and Padding

To understand why the Bad and Good structs from our example have different sizes, we need to look at how the CPU accesses memory. It's not a simple byte-by-byte process, and its efficiency depends heavily on where data is placed.

Word 1Word 2Word 3Word 1Word 2Bad Struct (24 byte)Good Struct (16 byte)IsActiveTotalTimePaddingAvgScore
Proper data structure alignment eliminates wasted memory by combining smaller fields to avoid padding

The CPU's Preferred Way to Read: Alignment

The CPU reads memory most efficiently in fixed-size chunks (usually 8 bytes on modern systems). It works best when data starts at memory addresses that are multiples of their size. For example:

What Happens When Data is Misaligned?

When data isn't properly aligned, the CPU has to work significantly harder to access it. Instead of efficiently reading a value in a single operation, the processor may need to read multiple memory chunks, combine relevant parts from these different reads, and then discard the unnecessary bytes. This extra computational work slows down your program's performance considerably.

How the Compiler Enforces Alignment

The compiler automatically adds invisible padding bytes between fields to ensure proper alignment. These padding bytes push subsequent fields to their ideal memory addresses. While this uses slightly more memory, it makes data access much faster by ensuring the CPU can read each field efficiently.

The Effortless Solution

Managing structs with 20+ fields across multiple application layers (controllers, services, repositories) quickly becomes overwhelming. Each layer often defines its own domain-specific struct, making manual optimization impractical.

Fortunately, Go provides an automated solution: the fieldalignment tool. This powerful analyzer automatically reorders struct fields to optimize memory layout and reduce padding. Here's how to implement it:

Install the tool globally:

go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest

Optimize your entire codebase:

fieldalignment -fix ./...

This command recursively scans all directories and automatically adjusts struct field ordering for optimal memory usage, eliminating the need for manual reorganization.

Conclusion

While struct field ordering might seem like a minor detail, it delivers substantial impact in large-scale Go applications. Poor field ordering wastes memory, slows data access, and adds unnecessary complexity as codebases grow.

With tools like fieldalignment, developers can automate this optimization, improving efficiency while focusing on building robust applications rather than micromanaging memory layouts. The best optimizations are often those that work silently in the background, and field ordering is a perfect example of this principle in action.