:depth: 2 :local:
MemProf is a profile-guided optimization (PGO) feature for memory. It enables the compiler to optimize memory allocations and static data layout based on runtime profiling information. By understanding the “hotness”, lifetime, and access frequency of memory, the compiler can make better decisions about where to place data (both heap and static), reducing fragmentation and improving cache locality.
Traditional PGO focuses on control flow (hot vs. cold code). MemProf extends this concept to data. It answers questions like:
This information enables optimizations such as:
This section describes how to use MemProf to profile and optimize your application.
To enable MemProf instrumentation, compile your application with the -fmemory-profile flag. Make sure to include debug information (-gmlt and -fdebug-info-for-profiling) and frame pointers to ensure accurate stack traces and line number reporting.
clang++ -fmemory-profile -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fno-optimize-sibling-calls -fdebug-info-for-profiling -gmlt -O2 -fno-pie -no-pie -Wl,-z,noseparate-code -Wl,--build-id source.cpp -o app
Link with `-fmemory-profile` as well to link the necessary runtime libraries. If you use a separate link step, ensure the flag is passed to the linker. On Linux, the flags `-fno-pie -no-pie -Wl,-z,noseparate-code -Wl,--build-id` are currently required to ensure the binary layout (executable segment at offset 0) and Build ID presence are compatible with the `llvm-profdata` profile reader.
Run the instrumented application. By default, MemProf writes a raw profile file named memprof.profraw.<pid> to the current directory upon exit. Control the runtime behavior using the MEMPROF_OPTIONS environment variable. Common options include:
log_path: Redirects runtime logs (e.g., stdout, stderr, or a file path).print_text: If set to true, prints a text-based summary of the profile to the log path.verbosity: Controls the level of debug output.Example:
MEMPROF_OPTIONS=log_path=stdout:print_text=true ./app
(processing-profiles)=
Raw profiles must be indexed before the compiler can use them. Use llvm-profdata to merge and index the raw profiles.
llvm-profdata merge memprof.profraw.* --profiled-binary ./app -o memprof.memprofdata
To dump the profile in YAML format (useful for debugging or creating test cases):
llvm-profdata show --memory memprof.memprofdata > memprof.yaml
Merge MemProf profiles with standard PGO instrumentation profiles if you have both (optional).
Feed the indexed profile back into the compiler using the -fmemory-profile-use= option (or low-level passes options).
clang++ -fmemory-profile-use=memprof.memprofdata -O2 -Wl,-mllvm,-enable-memprof-context-disambiguation -Wl,-mllvm,-optimize-hot-cold-new -Wl,-mllvm,-supports-hot-cold-new source.cpp -o optimized_app -ltcmalloc
If invoking the optimizer directly via opt:
opt -passes='memprof-use<profile-filename=memprof.memprofdata>' ...
The compiler uses the profile data to annotate allocation instructions with !memprof metadata (MemProf Metadata Documentation), distinguishing between “hot”, “cold”, and “notcold” allocations. This metadata guides downstream optimizations. Additionally, callsites which are part of allocation contexts are also annotated with !callsite metadata (Callsite Metadata Documentation).
Ensure that the same debug info flags (e.g. `-gmlt` and `-fdebug-info-for-profiling`) used during instrumentation are also passed during this compilation step to enable correct matching of the profile data. For the optimized binary to fully utilize the hot/cold hinting, it must be linked with an allocator that supports this mechanism, such as [tcmalloc](https://github.com/google/tcmalloc). TCMalloc provides an API (`tcmalloc::hot_cold_t`) that accepts a hint (0 for cold, 255 for hot) to guide data placement and improve locality. To indicate that the library supports these interfaces, the `-mllvm -supports-hot-cold-new` flag is used during the LTO link.
To fully benefit from MemProf, especially for common allocation wrappers, enabling ThinLTO (preferred) or Full LTO is required. This allows the compiler to perform context disambiguation.
Consider the following example:
void *allocate() { return new char[10]; } void hot_path() { // This path is executed frequently. allocate(); } void cold_path() { // This path is executed rarely. allocate(); }
Without context disambiguation, the compiler sees a single allocate function called from both hot and cold contexts. It must conservatively assume the allocation is “not cold” or “ambiguous”.
With LTO and MemProf:
allocate has distinct calling contexts with different behaviors.allocate into two versions: one for the hot path and one for the cold path.cold_path is updated to call the cloned “cold” version of allocate, which can then be optimized (e.g., by passing a cold hint to the allocator).MemProf profiles guide the layout of static data (e.g., global variables, constants). The goal is to separate “hot” data from “cold” data in the binary, placing hot data into specific sections (e.g., .rodata.hot) to minimize the number of pages required for the working set.
This feature uses a hybrid approach:
MEM_INST_RETIRED.ALL_LOADS).To enable this feature, pass the following flags to the compiler:
-fpartition-static-data-sections: Instructs the compiler to generate .hot and .unlikely section prefixes for hot and cold static data respectively in the relocatable object files.-Wl,-z,keep-data-section-prefix: Informs the LLD linker that .data.rel.ro.hot and .data.rel.ro.unlikely as relro sections. LLD requires all relro sections to be contiguous and this flag allows us to interleave the hotness-suffixed .data.rel.ro sections with other relro sections.-Wl,-script=<linker_script>: Group hot and/or cold data sections, and order the data sections.clang++ -fmemory-profile-use=memprof.memprofdata -fpartition-static-data-sections -fuse-ld=lld -Wl,-z,keep-data-section-prefix -O2 source.cpp -o optimized_app
The optimized layout clusters hot static data, improving dTLB and cache efficiency.
When both PGO profiles and memory profiles are provided (using `-fprofile-use` and `-fmemory-profile-use`), global variable hotness are inferred from a combination of PGO profile and data access profile: - For data covered by both profiles (e.g., module-internal data with symbols in the executable), the hotness is the max of PGO profile hotness and data access profile hotness. - For data covered by only one profile, the hotness is inferred from that profile. Most notably, symbolizable data with external linkage is only covered by data access profile, and module-internal unsymbolizable data is only covered by PGO profile.
This section provides an overview of the MemProf architecture and implementation for contributors.
MemProf consists of three main components:
The optimization process, using LTO, involves several steps:
!memprof and !callsite) is serialized into the module summary. This is implemented in llvm/lib/Analysis/ModuleSummaryAnalysis.cpp.CallsiteContextGraph to analyze and disambiguate contexts. This graph identifies where allocation contexts diverge (e.g., same function called from hot vs. cold paths). This logic resides in llvm/lib/Transforms/IPO/MemProfContextDisambiguation.cpp.CallsiteContextGraph.MemProfContextDisambiguation pass. The replacement of allocation calls (e.g., operator new to the hot_cold_t variant) happens in SimplifyLibCalls during the InstCombine pass. These transformations are guided by the decisions made during the LTO step.Runtime: compiler-rt/lib/memprof
Instrumentation: llvm/lib/Transforms/Instrumentation/MemProfInstrumentation.cpp
Profile Data: llvm/include/llvm/ProfileData/MemProf.h and MemProfData.inc
MemInfoBlock), and serialization logic.Use Pass: llvm/lib/Transforms/Instrumentation/MemProfUse.cpp
Context Disambiguation: llvm/lib/Transforms/IPO/MemProfContextDisambiguation.cpp
Transformation: llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
Static Data Partitioning: llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp, llvm/lib/CodeGen/StaticDataSplitter.cpp, and llvm/lib/CodeGen/StaticDataAnnotator.cpp
StaticDataAnnotator iterates over global variables and sets their section prefixes based on the profile analysis from StaticDataSplitter.The runtime uses a shadow memory scheme similar to AddressSanitizer (ASan) but optimized for profiling.
AllocCount, TotalAccessCount, TotalLifetime, and Min/MaxAccessDensity.The MemProf profile is a schema-based binary format designed for extensibility. Key structures include:
The format supports versioning to allow adding new fields to the MIB without breaking backward compatibility.
To support static data partitioning, the profile format includes a payload for symbolized data access profiles. This maps data addresses to canonical symbol names (or module source location for internal data) and access counts. This enables the compiler to identify which global variables are hot.
Testing
When making changes to MemProf, verify your changes using the following test suites:
Runtime Tests:
compiler-rt/test/memprofProfile Manipulation Tests:
llvm/test/tools/llvm-profdatallvm-profdata can correctly merge, show, and handle MemProf profiles.Instrumentation & Optimization Tests:
llvm/test/Transforms/PGOProfileMemProfUse pass, metadata annotation, and IR transformations.ThinLTO & Context Disambiguation Tests:
llvm/test/ThinLTO/X86/memprof* and llvm/test/Transforms/MemProfContextDisambiguationYou can create MemProf profiles in YAML format for testing purposes. This is useful for creating small, self-contained test cases without needing to run a binary.
Create a YAML Profile: You can start by dumping a real profile to YAML (see {ref}Processing Profiles <processing-profiles> above) or writing one from scratch.
Convert to Indexed Format: Use llvm-profdata to convert the YAML to the indexed MemProf format.
llvm-profdata merge --memprof-version=4 profile.yaml -o profile.memprofdata
Run the Compiler: Use the indexed profile with opt or clang.
opt -passes='memprof-use<profile-filename=profile.memprofdata>' test.ll -S
Example YAML Profile:
---
HeapProfileRecords:
- GUID: _Z3foov
AllocSites:
- Callstack:
- { Function: _Z3foov, LineOffset: 0, Column: 22, IsInlineFrame: false }
- { Function: main, LineOffset: 2, Column: 5, IsInlineFrame: false }
MemInfoBlock:
TotalSize: 400
AllocCount: 1
TotalLifetimeAccessDensity: 1
TotalLifetime: 1000000
CallSites: []
...