2026-06-29
You write a struct in C, the compiler dutifully pads it out, and you have no idea your hot data structure just grew from 24 bytes to 40 because you put a char next to a long. pahole — short for "poke-a-hole" — ships in the dwarves package (Arnaldo Carvalho de Melo, perf maintainer). Point it at any binary compiled with -g and it prints every struct layout, the exact offset of every field, the size of every hole, and where you cross a cacheline boundary.
Compile something with debug info and ask:
$ cc -g -c thing.c -o thing.o
$ pahole -C my_packet thing.o
struct my_packet {
char flag; /* 0 1 */
/* XXX 3 bytes hole, try to pack */
int seq; /* 4 4 */
char type; /* 8 1 */
/* XXX 7 bytes hole, try to pack */
long payload; /* 16 8 */
/* size: 24, cachelines: 1, members: 4 */
/* sum members: 14, holes: 2, sum holes: 10 */
/* last cacheline: 24 bytes */
};
Ten wasted bytes out of 24 — that's 42% air. pahole will also reorganize for you:
$ pahole --reorganize -C my_packet thing.o
struct my_packet {
long payload; /* 0 8 */
int seq; /* 8 4 */
char flag; /* 12 1 */
char type; /* 13 1 */
/* size: 16, cachelines: 1, members: 4 */
};
/* saved 8 bytes! */
For a struct allocated a million times, that's 8 MB of RAM and L1 pressure gone, for free.
Where the mainstream alternative fails: you can stare at a sizeof() all day and not know why it's 40 instead of 32. gdb ptype /o shows offsets but not holes, doesn't suggest a packing, and won't scan every struct in an object file. __builtin_offsetof() at runtime means recompiling and printing. pahole works on already-shipped binaries — including ones you didn't build.
Real tricks worth knowing:
pahole --packable /usr/lib/debug/.../libfoo.so.debug lists every type that can be tightened, sorted by bytes saved.pahole --cacheline_size=64 --show_only_data_members thing.o | grep -B1 'cachelines: [2-9]' — anything hot that spans two lines is a perf bug waiting to happen.pahole -C task_struct /sys/kernel/btf/vmlinux dumps the live struct layout with no debuginfo download. Indispensable when writing BPF programs that need exact offsets.codiff -s old.o new.o (ships in the same package) tells you which structs grew, which functions changed signature, and by how many bytes. Great for catching ABI drift in a CI job.pfunct -P thing.o — better than nm because you get arguments and return types from DWARF.pahole --suggest_ptr_layout proposes pointer-first ordering for 32/64-bit portable code.If you write performance-sensitive C, kernel modules, BPF, or wire protocols, pahole turns invisible padding into an editable text file. The Linux kernel uses it in tree to keep task_struct from blowing up; you can use it to keep your hot path inside one cacheline.
sizeof() tells you the bill; pahole tells you exactly which padding bytes are on it and how to delete them.
