Compile-Time Improvements in LLVM 23
LLVM 23 introduces significant compile-time enhancements, with a notable -6.75% improvement in -O3 builds, according to the source. This article delves into the key factors driving these gains. Hash maps/sets, integral to LLVM's functionality, undergo three substantial upgrades: swapping quadratically probed hash tables for linear probing and improved deletion (DenseMap, SmallPtrSet, StringMap) eliminates tombstone keys.
Occupancy in DenseMap is now tracked via a compact bit array, replacing empty keys, which not only saves on in-band reserved values but also reduces branch and cache misses. Simultaneously, the transition from CityHash and a weak pointer hash function to xxh3 (-0.18%) bolsters performance, especially with the legacy hash table. Within SmallVector, the push_back grow path is now out-of-line and optimized for tail call optimization (-0.50%), leading to shorter live ranges, fewer instructions, and reduced code size, facilitating more inlining.
The BumpAllocator sees minor clean-ups (-0.17%, +0.06%). Compile-time statistics exhibit mixed results due to inlining heuristics, with reallocation boundaries shifting based on the presence or size of D, occasionally missing simplifications achievable through inlining C into B. Iterators' post_order traversal has been rewritten (-0.18%), no longer storing state within the iterator itself, which improves iterator move efficiency and inlines certain iterator functions.
The dominator tree representation now adopts a child-sibling structure (-0.13%), avoiding allocations, provided the child order remains unchanged to maintain compatibility with various passes. The dominator tree construction sees notable improvements, notably skipping materialization of successors (-0.21%) and storing predecessors as an edge list (-0.11%), constituting the largest single gains.
Although the construction algorithm remains swift even for extensive programs (despite O(n^2) worst-case complexity), the dominator tree representation is still inefficient, primarily due to compatibility with existing traversal patterns and update support. Successors() is implemented as iterators over a range of Uses (-0.21%), addressing a longstanding inefficiency: prior to this change, each use access triggered an out-of-line function call, repeatedly dispatching based on the terminator instruction type.
This required preprocessing to ensure contiguous storage of successors in all terminators (SwitchInst needed adjustments, case values are now plain ConstantInt instead of Uses) and splitting Br opcode into UncondBr and CondBr (-0.08%) to avoid bitfield accesses for distinguishing these. Despite being a top 15 hottest function (self time) due to cache misses when accessing terminator opcode and branch misses at the switch on terminator type, these changes collectively enhance overall compile-time efficiency.
Written by urgent.news from Lobsters's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.