This is a list of general-purpose optimizations for C programs, from the most impactful to the tiniest low-level micro-optimizations to squeeze out every last bit of performance. It is meant to be read top-down as a checklist, with each item being a potential optimization to consider. Everything is in order of speed gain.
Choose the best algorithm and data structure for the problem at hand by evaluating:
- time complexity
- space complexity
- maintainability
Precompute values that are known at compile time using:
constexpr
sizeof()
- lookup tables
__attribute__((constructor))
Find tasks that can be split into smaller ones and run in parallel with:
Technique | Pros | Cons |
---|---|---|
SIMD | lightweight, fast | limited application, portability |
Async I/O | lightweight, zero waste of resources | only for I/O-bound tasks |
SWAR | lightweight, fast, portable | limited application, small chunks |
Multithreading | relatively lightweight, versatile | data races, corruption |
Multiprocessing | isolation, true parallelism | heavyweight, isolation |
Optimize memory access, duplication and stack size by using zero-copy techniques:
- pointers: avoid passing large data structures by value, pass pointers instead
- one for all: avoid passing multiple pointers of the same structure separately, pass a single pointer to a structure that contains them all
- memory-mapped I/O: avoid copying data from a file to memory, directly map the file to memory instead
- scatter-gather I/O: avoid copying data from multiple sources to a single destination, directly read/write from/to multiple sources/destinations instead
- dereferencing: avoid dereferencing pointers multiple times, store the dereferenced value in a variable and reuse that instead
Prioritize stack allocation for small data structures, and heap allocation for large data structures:
Alloc Type | Pros | Cons |
---|---|---|
Stack | Zero management overhead, fast, close to CPU cache | Limited size, scope-bound |
Heap | Persistent, large allocations | Higher latency (malloc/free overhead), fragmentation, memory leaks |
Reduce the overall number of function calls:
- System Functions: make fewer system calls as possible
- Library Functions: make fewer library calls as possible (unless linked statically)
- Recursive Functions: avoid recursion, use loops instead (unless tail-optmized)
- Inline Functions: inline small functions
Add compiler flags to automatically optimize the code, consider the side effects of each flag:
- -O3: general optimization
- -ffast-math: floating point optimizations
- -march=native: optimize for the current CPU
- -funroll-all-loops: unroll loops
- -fomit-frame-pointer: don't save the frame pointer
- -fno-stack-protector: disable stack protection
- -flto: link-time optimization
Minimize branching:
- Most Likely First: order if-else chains by most likely scenario first
- Switch: use switch statements or jump tables instead of if-else forests
- Sacrifice Short-Circuiting: don't immediately return if that implies using two separate if statements in the most likely scenario
- Combine if statements: combine multiple if statements into a single one, sacrificing short-circuiting if necessary
- Masks: use bitwise & and | instead of && and ||
Use aligned memory access:
__attribute__((aligned()))
: align stack variablesposix_memalign()
: align heap variables_mm_load
and_mm_store
: aligned SIMD memory access
Guide the compiler at optimizing hot paths:
__attribute__((hot))
: frequently called functions__attribute__((cold))
: rarely called functions__builtin_expect()
: most likely outcome of a conditional__builtin_assume_aligned()
: aligned memory accessunreachable()
: a certain path is unreachable (Don't Care)restrict
: two pointers don't overlapconst
: a variable is constant