betteralign is a tool that detects structs that would use less memory if their fields were reordered, and optionally rewrites them.
It is a fork of the official Go fieldalignment tool, with the bulk of the alignment logic unchanged. Notable differences include:
- skips generated files, identified either by a known suffix (
_generated.go,_gen.go,.gen.go,.pb.go,.pb.gw.go) or by a package-level comment containingCode generated by ... DO NOT EDIT., - skips test files (
_test.gosuffix), - skips structs annotated with
// betteralign:ignore, - supports opt-in mode, where only structs annotated with
// betteralign:checkare checked, - preserves comments (field comments, doc comments, and floating comments) — though comment position heuristics remain a work in progress,
- performs atomic file writes to prevent corruption or data loss on rewrite (not on Windows),
- includes more thorough tests comparing expected versus golden output,
- adapts automatically to CPU and memory constraints in containerized environments (Docker, Kubernetes, LXC, LXD, etc.).
Comment preservation is achieved using DST (Decorated Syntax Tree), which attaches comment decorations to AST nodes. The downside is that the entire file must be reprinted to retain those decorations — partial rewrites via SuggestedFixes are not possible. This is why the -fix flag from the analysis package has no effect, and why integration with golangci-lint is not supported.
Go's standard AST does not associate comments with nodes — it only stores byte offsets, so the original fieldalignment tool erases all comments. A proposed fix exists as an open CL but has not yet been merged.
Note: This is a single-pass tool. Achieving a fully optimal layout may require running it more than once.
This tool builds upon the following prior work:
- fieldalignment by the Go Authors
- maligned by Matthew Dempsky
- structslop by orijtech
betteralign pursues two goals:
- Minimize struct size by sorting fields in descending alignment order, reducing internal padding between fields.
- Reduce GC pointer scan overhead by grouping pointer-bearing fields before pointer-free ones (the GC stops scanning at the last pointer in a value).
With those goals in mind, fields are sorted stably by the following criteria, in order:
- Zero-sized types first.
- Higher alignment first.
- Pointer-bearing types before pointer-free types.
- Among pointer-bearing types, those with fewer trailing non-pointer bytes first (minimizing GC
ptrdata). - Larger size first.
Consider the following example:
package foo
type IPAddr struct {
Zone string // 16 bytes, 8-byte aligned
IP []byte // 24 bytes, 8-byte aligned
}Zone comes first because string has fewer trailing non-pointer bytes than []byte: Sizeof(string) - ptrdata(string) = 8 versus Sizeof([]byte) - ptrdata([]byte) = 16.
Manual: Download the appropriate binary from the releases page and place it in your PATH, typically /usr/local/bin/betteralign.
Via go install:
go install github.com/dkorunic/betteralign/cmd/betteralign@latestbetteralign: find structs that would use less memory if their fields were sorted
Usage: betteralign [-flag] [package]
This analyzer find structs that can be rearranged to use less memory, and provides
a suggested edit with the most compact order.
Note that there are two different diagnostics reported. One checks struct size,
and the other reports "pointer bytes" used. Pointer bytes is how many bytes of the
object that the garbage collector has to potentially scan for pointers, for example:
struct { uint32; string }
have 16 pointer bytes because the garbage collector has to scan up through the string's
inner pointer.
struct { string; *uint32 }
has 24 pointer bytes because it has to scan further through the *uint32.
struct { string; uint32 }
has 8 because it can stop immediately after the string pointer.
Be aware that the most compact order is not always the most efficient.
In rare cases it may cause two variables each updated by its own goroutine
to occupy the same CPU cache line, inducing a form of memory contention
known as "false sharing" that slows down both goroutines.
Unlike most analyzers, which report likely mistakes, the diagnostics
produced by betteralign very rarely indicate a significant problem,
so the analyzer is not included in typical suites such as vet or
gopls. Use this standalone command to run it on your code:
$ go install github.com/dkorunic/betteralign/cmd/betteralign@latest
$ betteralign [packages]
Flags:
-V print version and exit
-all
no effect (deprecated)
-apply
apply suggested fixes
-c int
display offending line with this many lines of context (default -1)
-cpuprofile string
write CPU profile to this file
-debug string
debug flags, any subset of "fpstv"
-diff
with -fix, don't update the files, but print a unified diff
-exclude_dirs value
exclude directories matching a pattern
-exclude_files value
exclude files matching a pattern
-fix
apply all suggested fixes
-flags
print analyzer flags in JSON
-generated_files
also check and fix generated files
-json
emit JSON output
-memprofile string
write memory profile to this file
-opt_in
opt-in mode on per-struct basis with 'betteralign:check' in comment
-source
no effect (deprecated)
-tags string
no effect (deprecated)
-test
indicates whether test files should be analyzed, too (default true)
-test_files
also check and fix test files
-trace string
write trace log to this file
-v no effect (deprecated)To check all packages in the current module:
betteralign ./...To automatically rewrite files (excluding test and generated files):
betteralign -apply ./...Generated and test files can be included with the -generated_files and -test_files flags respectively. Use -exclude_dirs and -exclude_files to skip specific paths. Use -opt_in to check only structs explicitly annotated with // betteralign:check.
