[llvm] [MemProf] Optionally print or record the profiled sizes of allocations (PR #98248)
Teresa Johnson via llvm-commits
llvm-commits at lists.llvm.org
Tue Jul 9 16:54:14 PDT 2024
https://github.com/teresajohnson created https://github.com/llvm/llvm-project/pull/98248
This is the first step in being able to track the total profiled sizes
of allocations successfully marked as cold.
Under a new option -memprof-report-hinted-sizes:
- For unambiguous (non-context-sensitive) allocations, print the
profiled size and the allocation coldness, along with a hash of the
allocation's location (to allow for deduplication across modules or
inline instances).
- For context sensitive allocations, add the size as a 3rd operand on
the MIB metadata. A follow on patch will propagate this through to the
thin link where the sizes will be reported for each context after
cloning.
>From 2b2438c4b9aad5909945dbc648400c246e241556 Mon Sep 17 00:00:00 2001
From: Teresa Johnson <tejohnson at google.com>
Date: Tue, 9 Jul 2024 16:43:25 -0700
Subject: [PATCH] [MemProf] Optionally print or record the profiled sizes of
allocations
This is the first step in being able to track the total profiled sizes
of allocations successfully marked as cold.
Under a new option -memprof-report-hinted-sizes:
- For unambiguous (non-context-sensitive) allocations, print the
profiled size and the allocation coldness, along with a hash of the
allocation's location (to allow for deduplication across modules or
inline instances).
- For context sensitive allocations, add the size as a 3rd operand on
the MIB metadata. A follow on patch will propagate this through to the
thin link where the sizes will be reported for each context after
cloning.
---
.../include/llvm/Analysis/MemoryProfileInfo.h | 12 +++--
llvm/lib/Analysis/MemoryProfileInfo.cpp | 44 ++++++++++++++-----
llvm/lib/IR/Verifier.cpp | 10 +++--
.../Instrumentation/MemProfiler.cpp | 11 ++++-
llvm/test/Transforms/PGOProfile/memprof.ll | 15 +++++++
llvm/test/Verifier/memprof-metadata-bad.ll | 4 +-
6 files changed, 76 insertions(+), 20 deletions(-)
diff --git a/llvm/include/llvm/Analysis/MemoryProfileInfo.h b/llvm/include/llvm/Analysis/MemoryProfileInfo.h
index 355bff46f6279..75ddf5cacf74d 100644
--- a/llvm/include/llvm/Analysis/MemoryProfileInfo.h
+++ b/llvm/include/llvm/Analysis/MemoryProfileInfo.h
@@ -37,6 +37,10 @@ MDNode *getMIBStackNode(const MDNode *MIB);
/// Returns the allocation type from an MIB metadata node.
AllocationType getMIBAllocType(const MDNode *MIB);
+/// Returns the total size from an MIB metadata node, or 0 if it was not
+/// recorded.
+uint64_t getMIBTotalSize(const MDNode *MIB);
+
/// Returns the string to use in attributes with the given type.
std::string getAllocTypeAttributeString(AllocationType Type);
@@ -54,10 +58,11 @@ class CallStackTrie {
// Allocation types for call context sharing the context prefix at this
// node.
uint8_t AllocTypes;
+ uint64_t TotalSize;
// Map of caller stack id to the corresponding child Trie node.
std::map<uint64_t, CallStackTrieNode *> Callers;
- CallStackTrieNode(AllocationType Type)
- : AllocTypes(static_cast<uint8_t>(Type)) {}
+ CallStackTrieNode(AllocationType Type, uint64_t TotalSize)
+ : AllocTypes(static_cast<uint8_t>(Type)), TotalSize(TotalSize) {}
};
// The node for the allocation at the root.
@@ -90,7 +95,8 @@ class CallStackTrie {
/// matching via a debug location hash), expected to be in order from the
/// allocation call down to the bottom of the call stack (i.e. callee to
/// caller order).
- void addCallStack(AllocationType AllocType, ArrayRef<uint64_t> StackIds);
+ void addCallStack(AllocationType AllocType, ArrayRef<uint64_t> StackIds,
+ uint64_t TotalSize = 0);
/// Add the call stack context along with its allocation type from the MIB
/// metadata to the Trie.
diff --git a/llvm/lib/Analysis/MemoryProfileInfo.cpp b/llvm/lib/Analysis/MemoryProfileInfo.cpp
index 5c09ba9462716..c81c65fd4e691 100644
--- a/llvm/lib/Analysis/MemoryProfileInfo.cpp
+++ b/llvm/lib/Analysis/MemoryProfileInfo.cpp
@@ -41,6 +41,10 @@ cl::opt<unsigned> MemProfMinAveLifetimeAccessDensityHotThreshold(
cl::desc("The minimum TotalLifetimeAccessDensity / AllocCount for an "
"allocation to be considered hot"));
+cl::opt<bool> MemProfReportHintedSizes(
+ "memprof-report-hinted-sizes", cl::init(false), cl::Hidden,
+ cl::desc("Report total allocation sizes of hinted allocations"));
+
AllocationType llvm::memprof::getAllocType(uint64_t TotalLifetimeAccessDensity,
uint64_t AllocCount,
uint64_t TotalLifetime) {
@@ -74,13 +78,13 @@ MDNode *llvm::memprof::buildCallstackMetadata(ArrayRef<uint64_t> CallStack,
}
MDNode *llvm::memprof::getMIBStackNode(const MDNode *MIB) {
- assert(MIB->getNumOperands() == 2);
+ assert(MIB->getNumOperands() >= 2);
// The stack metadata is the first operand of each memprof MIB metadata.
return cast<MDNode>(MIB->getOperand(0));
}
AllocationType llvm::memprof::getMIBAllocType(const MDNode *MIB) {
- assert(MIB->getNumOperands() == 2);
+ assert(MIB->getNumOperands() >= 2);
// The allocation type is currently the second operand of each memprof
// MIB metadata. This will need to change as we add additional allocation
// types that can be applied based on the allocation profile data.
@@ -94,6 +98,12 @@ AllocationType llvm::memprof::getMIBAllocType(const MDNode *MIB) {
return AllocationType::NotCold;
}
+uint64_t llvm::memprof::getMIBTotalSize(const MDNode *MIB) {
+ if (MIB->getNumOperands() < 3)
+ return 0;
+ return mdconst::dyn_extract<ConstantInt>(MIB->getOperand(2))->getZExtValue();
+}
+
std::string llvm::memprof::getAllocTypeAttributeString(AllocationType Type) {
switch (Type) {
case AllocationType::NotCold:
@@ -125,7 +135,8 @@ bool llvm::memprof::hasSingleAllocType(uint8_t AllocTypes) {
}
void CallStackTrie::addCallStack(AllocationType AllocType,
- ArrayRef<uint64_t> StackIds) {
+ ArrayRef<uint64_t> StackIds,
+ uint64_t TotalSize) {
bool First = true;
CallStackTrieNode *Curr = nullptr;
for (auto StackId : StackIds) {
@@ -135,9 +146,10 @@ void CallStackTrie::addCallStack(AllocationType AllocType,
if (Alloc) {
assert(AllocStackId == StackId);
Alloc->AllocTypes |= static_cast<uint8_t>(AllocType);
+ Alloc->TotalSize += TotalSize;
} else {
AllocStackId = StackId;
- Alloc = new CallStackTrieNode(AllocType);
+ Alloc = new CallStackTrieNode(AllocType, TotalSize);
}
Curr = Alloc;
continue;
@@ -147,10 +159,11 @@ void CallStackTrie::addCallStack(AllocationType AllocType,
if (Next != Curr->Callers.end()) {
Curr = Next->second;
Curr->AllocTypes |= static_cast<uint8_t>(AllocType);
+ Curr->TotalSize += TotalSize;
continue;
}
// Otherwise add a new caller node.
- auto *New = new CallStackTrieNode(AllocType);
+ auto *New = new CallStackTrieNode(AllocType, TotalSize);
Curr->Callers[StackId] = New;
Curr = New;
}
@@ -167,16 +180,19 @@ void CallStackTrie::addCallStack(MDNode *MIB) {
assert(StackId);
CallStack.push_back(StackId->getZExtValue());
}
- addCallStack(getMIBAllocType(MIB), CallStack);
+ addCallStack(getMIBAllocType(MIB), CallStack, getMIBTotalSize(MIB));
}
static MDNode *createMIBNode(LLVMContext &Ctx,
std::vector<uint64_t> &MIBCallStack,
- AllocationType AllocType) {
+ AllocationType AllocType, uint64_t TotalSize) {
std::vector<Metadata *> MIBPayload(
{buildCallstackMetadata(MIBCallStack, Ctx)});
MIBPayload.push_back(
MDString::get(Ctx, getAllocTypeAttributeString(AllocType)));
+ if (TotalSize)
+ MIBPayload.push_back(ValueAsMetadata::get(
+ ConstantInt::get(Type::getInt64Ty(Ctx), TotalSize)));
return MDNode::get(Ctx, MIBPayload);
}
@@ -190,8 +206,8 @@ bool CallStackTrie::buildMIBNodes(CallStackTrieNode *Node, LLVMContext &Ctx,
// Trim context below the first node in a prefix with a single alloc type.
// Add an MIB record for the current call stack prefix.
if (hasSingleAllocType(Node->AllocTypes)) {
- MIBNodes.push_back(
- createMIBNode(Ctx, MIBCallStack, (AllocationType)Node->AllocTypes));
+ MIBNodes.push_back(createMIBNode(
+ Ctx, MIBCallStack, (AllocationType)Node->AllocTypes, Node->TotalSize));
return true;
}
@@ -227,7 +243,8 @@ bool CallStackTrie::buildMIBNodes(CallStackTrieNode *Node, LLVMContext &Ctx,
// non-cold allocation type.
if (!CalleeHasAmbiguousCallerContext)
return false;
- MIBNodes.push_back(createMIBNode(Ctx, MIBCallStack, AllocationType::NotCold));
+ MIBNodes.push_back(createMIBNode(Ctx, MIBCallStack, AllocationType::NotCold,
+ Node->TotalSize));
return true;
}
@@ -238,6 +255,13 @@ bool CallStackTrie::buildAndAttachMIBMetadata(CallBase *CI) {
auto &Ctx = CI->getContext();
if (hasSingleAllocType(Alloc->AllocTypes)) {
addAllocTypeAttribute(Ctx, CI, (AllocationType)Alloc->AllocTypes);
+ if (MemProfReportHintedSizes) {
+ assert(Alloc->TotalSize);
+ errs() << "Total size for allocation with location hash " << AllocStackId
+ << " and single alloc type "
+ << getAllocTypeAttributeString((AllocationType)Alloc->AllocTypes)
+ << ": " << Alloc->TotalSize << "\n";
+ }
return false;
}
std::vector<uint64_t> MIBCallStack;
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index c98f61d555140..1794dd3cc028b 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -4934,10 +4934,14 @@ void Verifier::visitMemProfMetadata(Instruction &I, MDNode *MD) {
MDNode *StackMD = dyn_cast<MDNode>(MIB->getOperand(0));
visitCallStackMetadata(StackMD);
- // Check that remaining operands are MDString.
- Check(llvm::all_of(llvm::drop_begin(MIB->operands()),
+ // Check that remaining operands, except possibly the last, are MDString.
+ Check(llvm::all_of(MIB->operands().drop_front().drop_back(),
[](const MDOperand &Op) { return isa<MDString>(Op); }),
- "Not all !memprof MemInfoBlock operands 1 to N are MDString", MIB);
+ "Not all !memprof MemInfoBlock operands 1 to N-1 are MDString", MIB);
+ // The last operand might be the total profiled size so can be an integer.
+ auto &LastOperand = MIB->operands().back();
+ Check(isa<MDString>(LastOperand) || mdconst::hasa<ConstantInt>(LastOperand),
+ "Last !memprof MemInfoBlock operand not MDString or int", MIB);
}
}
diff --git a/llvm/lib/Transforms/Instrumentation/MemProfiler.cpp b/llvm/lib/Transforms/Instrumentation/MemProfiler.cpp
index 5d5e205d45462..2c5d749d4a67a 100644
--- a/llvm/lib/Transforms/Instrumentation/MemProfiler.cpp
+++ b/llvm/lib/Transforms/Instrumentation/MemProfiler.cpp
@@ -161,6 +161,8 @@ static cl::opt<bool>
"context in this module's profiles"),
cl::Hidden, cl::init(false));
+extern cl::opt<bool> MemProfReportHintedSizes;
+
// Instrumentation statistics
STATISTIC(NumInstrumentedReads, "Number of instrumented reads");
STATISTIC(NumInstrumentedWrites, "Number of instrumented writes");
@@ -712,7 +714,12 @@ static AllocationType addCallStack(CallStackTrie &AllocTrie,
auto AllocType = getAllocType(AllocInfo->Info.getTotalLifetimeAccessDensity(),
AllocInfo->Info.getAllocCount(),
AllocInfo->Info.getTotalLifetime());
- AllocTrie.addCallStack(AllocType, StackIds);
+ uint64_t TotalSize = 0;
+ if (MemProfReportHintedSizes) {
+ TotalSize = AllocInfo->Info.getTotalSize();
+ assert(TotalSize);
+ }
+ AllocTrie.addCallStack(AllocType, StackIds, TotalSize);
return AllocType;
}
@@ -1055,4 +1062,4 @@ PreservedAnalyses MemProfUsePass::run(Module &M, ModuleAnalysisManager &AM) {
}
return PreservedAnalyses::none();
-}
\ No newline at end of file
+}
diff --git a/llvm/test/Transforms/PGOProfile/memprof.ll b/llvm/test/Transforms/PGOProfile/memprof.ll
index 4a87f4f9d7449..b6407f7b123d3 100644
--- a/llvm/test/Transforms/PGOProfile/memprof.ll
+++ b/llvm/test/Transforms/PGOProfile/memprof.ll
@@ -63,6 +63,9 @@
;; give both memprof and pgo metadata.
; RUN: opt < %s -passes='pgo-instr-use,memprof-use<profile-filename=%t.pgomemprofdata>' -pgo-test-profile-file=%t.pgomemprofdata -pgo-warn-missing-function -S 2>&1 | FileCheck %s --check-prefixes=MEMPROF,ALL,PGO
+;; Check that the total sizes are reported if requested.
+; RUN: opt < %s -passes='memprof-use<profile-filename=%t.memprofdata>' -pgo-warn-missing-function -S -memprof-report-hinted-sizes 2>&1 | FileCheck %s --check-prefixes=TOTALSIZES
+
; MEMPROFMATCHINFO: MemProf notcold context with id 1093248920606587996 has total profiled size 10 is matched
; MEMPROFMATCHINFO: MemProf notcold context with id 5725971306423925017 has total profiled size 10 is matched
; MEMPROFMATCHINFO: MemProf notcold context with id 6792096022461663180 has total profiled size 10 is matched
@@ -331,6 +334,18 @@ for.end: ; preds = %for.cond
; MEMPROF: ![[C10]] = !{i64 2061451396820446691}
; MEMPROF: ![[C11]] = !{i64 1544787832369987002}
+;; For non-context sensitive allocations that get attributes we emit a message
+;; with the allocation hash, type, and size in bytes.
+; TOTALSIZES: Total size for allocation with location hash 6792096022461663180 and single alloc type notcold: 10
+; TOTALSIZES: Total size for allocation with location hash 15737101490731057601 and single alloc type cold: 10
+;; For context sensitive allocations the size in bytes is included on the MIB
+;; metadata.
+; TOTALSIZES: !"cold", i64 10}
+; TOTALSIZES: !"cold", i64 10}
+; TOTALSIZES: !"notcold", i64 10}
+; TOTALSIZES: !"cold", i64 20}
+; TOTALSIZES: !"notcold", i64 10}
+
; MEMPROFNOCOLINFO: #[[A1]] = { builtin allocsize(0) "memprof"="notcold" }
; MEMPROFNOCOLINFO: #[[A2]] = { builtin allocsize(0) "memprof"="cold" }
diff --git a/llvm/test/Verifier/memprof-metadata-bad.ll b/llvm/test/Verifier/memprof-metadata-bad.ll
index 83a10764d1808..f4f1f6bb0a463 100644
--- a/llvm/test/Verifier/memprof-metadata-bad.ll
+++ b/llvm/test/Verifier/memprof-metadata-bad.ll
@@ -43,8 +43,8 @@ declare dso_local noalias noundef ptr @malloc(i64 noundef)
!6 = !{i64 0}
!7 = !{!8}
; CHECK: call stack metadata should have at least 1 operand
-; CHECK: Not all !memprof MemInfoBlock operands 1 to N are MDString
-!8 = !{!0, !"default", i64 0}
+; CHECK: Not all !memprof MemInfoBlock operands 1 to N-1 are MDString
+!8 = !{!0, !"default", i64 0, i64 5}
!9 = !{i64 123}
; CHECK: call stack metadata operand should be constant integer
!10 = !{!"wrongtype"}
More information about the llvm-commits
mailing list