RFC Amendment: Eytzinger Name Table and Parallel Function Offsets
Problem Statement
The compiler spends a significant amount of time loading and parsing
SecFuncOffsetTable in large AutoFDO profiles. The problem lies in the
encoding of the section where we use pairs of variable-length ULEB128 integers.
Since we cannot perform random access on the mmap memory, we must eagerly load
the entire section regardless of how many entries the current module is
interested in retrieving.
Background
Here is how we use SecFuncOffsetTable:
-
The compiler invokes
getSamplesFor. -
We look up its GUID in
SecFuncOffsetTable(or its in-memory equivalent). -
We find the corresponding file offset in
SecLBRProfile(relative to the
beginning of the section). -
We retrieve
FunctionSamples
In other words, we are essentially operating a hash map, where
SecFuncOffsetTable acts as key-value pairs, with the value being a file
offset.
Solution
The solution below applies to those cases where the user wishes to generate a
MD5 profile. The raw string-based AutoFDO profile is out of scope.
We propose a space-efficient profile format that does not require eager loading.
The basic setup is parallel arrays:
-
SecNameTable(orSecCSNameTable): Continues to be an array of MD5 values
in fixed lengthuint64_t. -
SecFuncOffsetTable: This section will hold an array of file offsets in
uint32_ttoFunctionSamplesinSecLBRProfileonly. It will no longer
hold any keys (GUIDs).
SecNameTable
Now, we have three mutually exclusive sets of GUIDs:
- CSKeys: Those GUIDs used as keys in Context-Sensitive
SecLBRProfile - FlatKeys: Those GUIDs used as keys in Flat
SecLBRProfile - Inlinees: Those GUIDs mentioned in
SecLBRProfilebut not as keys
The new SecNameTable will consist of:
- Metadata
- the number of CSKeys
- the number of FlatKeys
- the number of Inlinees
- Payload
- CSKeys in Eytzinger layout
- FlatKeys in Eytzinger layout
- Inlinees in Eytzinger layout
Notice that we have exactly the same number of keys as before, but they are
arranged differently into three groups, each of which can be easily searched
with a binary search.
SecFuncOffsetTable
The SecFuncOffsetTable for the Context-Sensitive SecLBRProfile has as many
entries as CSKeys, each being fixed-length uint32_t. We form key-value pairs
as follows:
-
Key (GUID): The k-th entry of CSKeys in
SecNameTable -
Value (File offset): The k-th entry of
SecFuncOffsetTablefor
Context-SensitiveSecLBRProfile. This file offset is within
SecLBRProfilejust as before.
The same setup applies to SecFuncOffsetTable for Flat SecLBRProfile.
SecLBRProfile
This section encodes GUIDs as indexes into the name table as ULEB128. The
encoding scheme stays the same as before, but since the name table is in a new
order, actual encodings of ULEB128 will change.
Preliminary Results
Compilation Performance
Benchmarking on internal AutoFDO application using a large MD5-based profile
shows the following speed up with the new format:
| Compilation Step | Speedup Improvement |
|---|---|
| ThinLTO Pre-link Compilation | 34.5% speedup |
| ThinLTO Post-link Backend | 79.0% speedup |
Profile Size on Disk
The same XFDO application above has 794,393 top-level function symbols across
its split hot and cold SecFuncOffsetTable sections. Replacing ULEB128 pairs
with a parallel 4-byte (uint32_t) array reduces the section size on disk
by 53%:
- Before (
ULEB128 Pairs): 6.73 MB across both sections. - After (Parallel
uint32_tArray): 3.18 MB across both sections.
Impact on Heap Allocation
- Before (
SecFuncOffsetTable): 17.8 MB forDenseMapto store
622,380 top-level entries from the larger hot/CS section after eagerly
decoding ULEB128 pairs. - After (Parallel
uint32_tArray): 80 bytes for various metadata.
The reader operates directly over memory-mapped disk slices.
Auxiliary Benefits
Fast Global Membership Queries
The three Eytzinger spans in SampleProfileNameTable let
SampleProfileLoader::runOnFunction check symbol membership instantly on
demand, eliminating upfront DenseSet (GUIDsInProfile) construction.
Synergy with Profile Symbol List
As shown in:
I’m planning to encode Profile Symbol List in Eytzinger layout. We can use the
same encoder and decoder (binary search).
- The decoder: [ADT] Introduce EytzingerTableSpan by kazutakahirata · Pull Request #208885 · llvm/llvm-project · GitHub
- The encoder: [ADT] Introduce EytzingerTable by kazutakahirata · Pull Request #209343 · llvm/llvm-project · GitHub
On-Disk Hash Table
Earlier, I was planning to use OnDiskChainedHashTable in SecFuncOffsetTable:
I have since shelved the idea for the following reasons:
-
OnDiskChainedHashTableuses about 6 times as much disk space for
SecFuncOffsetTable. -
OnDiskChainedHashTableis an one-off solution with no synergy with Profile
Symbol List in Eytzinger layout.
Upstreaming
-
Finish upstreaming Eytzinger encoder. (The decoder has already landed.)
-
Support Eytzinger layout in
SecNameTable(while leavingSecFuncOffsetTable
as is in terms of the encoding scheme). -
Use a
uint32_tarray inSecFuncOffsetTable.