Background
We’ve noticed that the sample profile loader in the compiler spends a lot of time loading the profile. In one compilation, the compiler spends about 11% of time on:
llvm::sampleprof::SampleProfileReaderExtBinaryBase::readImpl
This is due to the fact that the sample profile loader eagerly loads two sections SecFuncOffsetTable and SecNameTable in their entirety even though the compiler references a tiny fraction of the contents.
Design
We will lazily load two sections SecFuncOffsetTable and SecNameTable. That is, we load the metadata first and then load the actual data only upon request.
Combining SecFuncOffsetTable and SecLBRProfile
We propose to combine SecFuncOffsetTable and SecLBRProfile into a single, combined section: SecLBRProfileHashTable.
Current Format
SecFuncOffsetTable(serialized map):- Key: An index, expressed in ULEB128, into the NameTable (an array of MD5 hashes)
- Value: A byte offset, expressed in ULEB128, into the serialized
FunctionSamplesrecords inSecLBRProfile - Note: This is the map that we read into
DenseMapat startup.
SecLBRProfile(serialized variable-length records):- Contains the actual
FunctionSamplesdata. Because they are variable-sized, the offsets fromSecFuncOffsetTableare required for random access (on-demand loading).
- Contains the actual
New Format
The proposed SecLBRProfileHashTable will combine the two sections above into a single section SecLBRProfileHashTable, implemented with OnDiskChainedHashTable.
- Key: 32-bit index into the NameTable
- Value:
FunctionSamples
The sample profile loader continues to recognize the old format and read from it if present.
Expected Performance
- Compile time: We expect to save 9% of time spent on AutoFDO ThinLTO pre-link and ThinLTO backend compilations when using very large, multi-target profiles (e.g., a 2 GB profile containing 10M+ name entries).
- Profile file size: We expect to increase the AutoFDO profile file size by about 15 bytes per profiled function. The increase primarily comes from the overhead of the on-disk hash table, such as the index structure and hash values.
- Heap size: We expect to save about 32 bytes of heap memory per entry for not having to construct a
DenseMapfromSecFuncOffsetTable. (16 bytes per entry divided by the average load factor of 0.5)
Lazy-Load SecNameTable
We propose to lazily load SecNameTable without changing the format.
Current Method
The SecNameTable section consists of an array of 8-byte MD5 hash values of symbol names, without any padding in between. In the fixed MD5 mode, SampleProfileReaderExtBinaryBase::readNameTableSec eagerly loads the entire section and constructs a 16-byte structure FunctionId for each MD5 hash value.
New Method
The reader will parse and retain the section’s metadata like the file offset of this array. FunctionId instances are then materialized on-demand.
Expected Performance
- Compile Time: We expect to save 1% of time spent on AutoFDO ThinLTO pre-link and ThinLTO backend compilations.
- Profile file size: No change because the format stays the same.
- Heap size: We expect to save 16 bytes per name table entry (≈ 16 MB per million entries).