Skip to content

Commit 9513f2f

Browse files
[MemProf] Print full context hash when reporting hinted bytes (#114465)
Improve the information printed when -memprof-report-hinted-sizes is enabled. Now print the full context hash computed from the original profile, similar to what we do when reporting matching statistics. This will make it easier to correlate with the profile. Note that the full context hash must be computed at profile match time and saved in the metadata and summary, because we may trim the context during matching when it isn't needed for distinguishing hotness. Similarly, due to the context trimming, we may have more than one full context id and total size pair per MIB in the metadata and summary, which now get a list of these pairs. Remove the old aggregate size from the metadata and summary support. One other change from the prior support is that we no longer write the size information into the combined index for the LTO backends, which don't use this information, which reduces unnecessary bloat in distributed index files.
1 parent f6e1d64 commit 9513f2f

File tree

19 files changed

+372
-157
lines changed

19 files changed

+372
-157
lines changed

llvm/include/llvm/Analysis/MemoryProfileInfo.h

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,17 @@ AllocationType getAllocType(uint64_t TotalLifetimeAccessDensity,
2828
/// the resulting metadata node.
2929
MDNode *buildCallstackMetadata(ArrayRef<uint64_t> CallStack, LLVMContext &Ctx);
3030

31+
/// Build metadata from the provided list of full stack id and profiled size, to
32+
/// use when reporting of hinted sizes is enabled.
33+
MDNode *buildContextSizeMetadata(ArrayRef<ContextTotalSize> ContextSizeInfo,
34+
LLVMContext &Ctx);
35+
3136
/// Returns the stack node from an MIB metadata node.
3237
MDNode *getMIBStackNode(const MDNode *MIB);
3338

3439
/// Returns the allocation type from an MIB metadata node.
3540
AllocationType getMIBAllocType(const MDNode *MIB);
3641

37-
/// Returns the total size from an MIB metadata node, or 0 if it was not
38-
/// recorded.
39-
uint64_t getMIBTotalSize(const MDNode *MIB);
40-
4142
/// Returns the string to use in attributes with the given type.
4243
std::string getAllocTypeAttributeString(AllocationType Type);
4344

@@ -55,11 +56,15 @@ class CallStackTrie {
5556
// Allocation types for call context sharing the context prefix at this
5657
// node.
5758
uint8_t AllocTypes;
58-
uint64_t TotalSize;
59+
// If the user has requested reporting of hinted sizes, keep track of the
60+
// associated full stack id and profiled sizes. Can have more than one
61+
// after trimming (e.g. when building from metadata). This is only placed on
62+
// the last (root-most) trie node for each allocation context.
63+
std::vector<ContextTotalSize> ContextSizeInfo;
5964
// Map of caller stack id to the corresponding child Trie node.
6065
std::map<uint64_t, CallStackTrieNode *> Callers;
61-
CallStackTrieNode(AllocationType Type, uint64_t TotalSize)
62-
: AllocTypes(static_cast<uint8_t>(Type)), TotalSize(TotalSize) {}
66+
CallStackTrieNode(AllocationType Type)
67+
: AllocTypes(static_cast<uint8_t>(Type)) {}
6368
};
6469

6570
// The node for the allocation at the root.
@@ -75,6 +80,11 @@ class CallStackTrie {
7580
delete Node;
7681
}
7782

83+
// Recursively build up a complete list of context size information from the
84+
// trie nodes reached form the given Node, for hint size reporting.
85+
void collectContextSizeInfo(CallStackTrieNode *Node,
86+
std::vector<ContextTotalSize> &ContextSizeInfo);
87+
7888
// Recursive helper to trim contexts and create metadata nodes.
7989
bool buildMIBNodes(CallStackTrieNode *Node, LLVMContext &Ctx,
8090
std::vector<uint64_t> &MIBCallStack,
@@ -93,7 +103,7 @@ class CallStackTrie {
93103
/// allocation call down to the bottom of the call stack (i.e. callee to
94104
/// caller order).
95105
void addCallStack(AllocationType AllocType, ArrayRef<uint64_t> StackIds,
96-
uint64_t TotalSize = 0);
106+
std::vector<ContextTotalSize> ContextSizeInfo = {});
97107

98108
/// Add the call stack context along with its allocation type from the MIB
99109
/// metadata to the Trie.

llvm/include/llvm/Bitcode/LLVMBitCodes.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ enum GlobalValueSummarySymtabCodes {
308308
FS_PERMODULE_CALLSITE_INFO = 26,
309309
// Summary of per-module allocation memprof metadata.
310310
// [nummib, nummib x (alloc type, numstackids, numstackids x stackidindex),
311-
// [nummib x total size]?]
311+
// [nummib x (numcontext x total size)]?]
312312
FS_PERMODULE_ALLOC_INFO = 27,
313313
// Summary of combined index memprof callsite metadata.
314314
// [valueid, numstackindices, numver,
@@ -317,9 +317,20 @@ enum GlobalValueSummarySymtabCodes {
317317
// Summary of combined index allocation memprof metadata.
318318
// [nummib, numver,
319319
// nummib x (alloc type, numstackids, numstackids x stackidindex),
320-
// numver x version, [nummib x total size]?]
320+
// numver x version]
321321
FS_COMBINED_ALLOC_INFO = 29,
322+
// List of all stack ids referenced by index in the callsite and alloc infos.
323+
// [n x stack id]
322324
FS_STACK_IDS = 30,
325+
// List of all full stack id pairs corresponding to the total sizes recorded
326+
// at the end of the alloc info when reporting of hinted bytes is enabled.
327+
// We use a fixed-width array, which is more efficient as these ids typically
328+
// are close to 64 bits in size. The max fixed width value supported is 32
329+
// bits so each 64-bit context id hash is recorded as a pair (upper 32 bits
330+
// first). This record must immediately precede the associated alloc info, and
331+
// the entries must be in the exact same order as the corresponding sizes.
332+
// [nummib x (numcontext x full stack id)]
333+
FS_ALLOC_CONTEXT_IDS = 31,
323334
};
324335

325336
enum MetadataCodes {

llvm/include/llvm/IR/ModuleSummaryIndex.h

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,14 @@ template <> struct DenseMapInfo<ValueInfo> {
302302
static unsigned getHashValue(ValueInfo I) { return (uintptr_t)I.getRef(); }
303303
};
304304

305+
// For optional hinted size reporting, holds a pair of the full stack id
306+
// (pre-trimming, from the full context in the profile), and the associated
307+
// total profiled size.
308+
struct ContextTotalSize {
309+
uint64_t FullStackId;
310+
uint64_t TotalSize;
311+
};
312+
305313
/// Summary of memprof callsite metadata.
306314
struct CallsiteInfo {
307315
// Actual callee function.
@@ -408,9 +416,13 @@ struct AllocInfo {
408416
// Vector of MIBs in this memprof metadata.
409417
std::vector<MIBInfo> MIBs;
410418

411-
// If requested, keep track of total profiled sizes for each MIB. This will be
412-
// a vector of the same length and order as the MIBs vector, if non-empty.
413-
std::vector<uint64_t> TotalSizes;
419+
// If requested, keep track of full stack contexts and total profiled sizes
420+
// for each MIB. This will be a vector of the same length and order as the
421+
// MIBs vector, if non-empty. Note that each MIB in the summary can have
422+
// multiple of these as we trim the contexts when possible during matching.
423+
// For hinted size reporting we, however, want the original pre-trimmed full
424+
// stack context id for better correlation with the profile.
425+
std::vector<std::vector<ContextTotalSize>> ContextSizeInfos;
414426

415427
AllocInfo(std::vector<MIBInfo> MIBs) : MIBs(std::move(MIBs)) {
416428
Versions.push_back(0);
@@ -432,14 +444,18 @@ inline raw_ostream &operator<<(raw_ostream &OS, const AllocInfo &AE) {
432444
for (auto &M : AE.MIBs) {
433445
OS << "\t\t" << M << "\n";
434446
}
435-
if (!AE.TotalSizes.empty()) {
436-
OS << " TotalSizes per MIB:\n\t\t";
437-
First = true;
438-
for (uint64_t TS : AE.TotalSizes) {
439-
if (!First)
440-
OS << ", ";
441-
First = false;
442-
OS << TS << "\n";
447+
if (!AE.ContextSizeInfos.empty()) {
448+
OS << "\tContextSizeInfo per MIB:\n";
449+
for (auto Infos : AE.ContextSizeInfos) {
450+
OS << "\t\t";
451+
bool FirstInfo = true;
452+
for (auto [FullStackId, TotalSize] : Infos) {
453+
if (!FirstInfo)
454+
OS << ", ";
455+
FirstInfo = false;
456+
OS << "{ " << FullStackId << ", " << TotalSize << " }";
457+
}
458+
OS << "\n";
443459
}
444460
}
445461
return OS;

llvm/lib/Analysis/MemoryProfileInfo.cpp

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,6 @@ AllocationType llvm::memprof::getMIBAllocType(const MDNode *MIB) {
100100
return AllocationType::NotCold;
101101
}
102102

103-
uint64_t llvm::memprof::getMIBTotalSize(const MDNode *MIB) {
104-
if (MIB->getNumOperands() < 3)
105-
return 0;
106-
return mdconst::dyn_extract<ConstantInt>(MIB->getOperand(2))->getZExtValue();
107-
}
108-
109103
std::string llvm::memprof::getAllocTypeAttributeString(AllocationType Type) {
110104
switch (Type) {
111105
case AllocationType::NotCold:
@@ -136,22 +130,21 @@ bool llvm::memprof::hasSingleAllocType(uint8_t AllocTypes) {
136130
return NumAllocTypes == 1;
137131
}
138132

139-
void CallStackTrie::addCallStack(AllocationType AllocType,
140-
ArrayRef<uint64_t> StackIds,
141-
uint64_t TotalSize) {
133+
void CallStackTrie::addCallStack(
134+
AllocationType AllocType, ArrayRef<uint64_t> StackIds,
135+
std::vector<ContextTotalSize> ContextSizeInfo) {
142136
bool First = true;
143137
CallStackTrieNode *Curr = nullptr;
144138
for (auto StackId : StackIds) {
145-
// If this is the first stack frame, add or update alloc node.
139+
// If this is the first stack frame, add or update alloc node.
146140
if (First) {
147141
First = false;
148142
if (Alloc) {
149143
assert(AllocStackId == StackId);
150144
Alloc->AllocTypes |= static_cast<uint8_t>(AllocType);
151-
Alloc->TotalSize += TotalSize;
152145
} else {
153146
AllocStackId = StackId;
154-
Alloc = new CallStackTrieNode(AllocType, TotalSize);
147+
Alloc = new CallStackTrieNode(AllocType);
155148
}
156149
Curr = Alloc;
157150
continue;
@@ -161,15 +154,18 @@ void CallStackTrie::addCallStack(AllocationType AllocType,
161154
if (Next != Curr->Callers.end()) {
162155
Curr = Next->second;
163156
Curr->AllocTypes |= static_cast<uint8_t>(AllocType);
164-
Curr->TotalSize += TotalSize;
165157
continue;
166158
}
167159
// Otherwise add a new caller node.
168-
auto *New = new CallStackTrieNode(AllocType, TotalSize);
160+
auto *New = new CallStackTrieNode(AllocType);
169161
Curr->Callers[StackId] = New;
170162
Curr = New;
171163
}
172164
assert(Curr);
165+
Curr->ContextSizeInfo.insert(Curr->ContextSizeInfo.end(),
166+
ContextSizeInfo.begin(), ContextSizeInfo.end());
167+
std::vector<ContextTotalSize> AllContextSizeInfo;
168+
collectContextSizeInfo(Curr, AllContextSizeInfo);
173169
}
174170

175171
void CallStackTrie::addCallStack(MDNode *MIB) {
@@ -182,21 +178,52 @@ void CallStackTrie::addCallStack(MDNode *MIB) {
182178
assert(StackId);
183179
CallStack.push_back(StackId->getZExtValue());
184180
}
185-
addCallStack(getMIBAllocType(MIB), CallStack, getMIBTotalSize(MIB));
181+
std::vector<ContextTotalSize> ContextSizeInfo;
182+
// Collect the context size information if it exists.
183+
if (MIB->getNumOperands() > 2) {
184+
for (unsigned I = 2; I < MIB->getNumOperands(); I++) {
185+
MDNode *ContextSizePair = dyn_cast<MDNode>(MIB->getOperand(I));
186+
assert(ContextSizePair->getNumOperands() == 2);
187+
uint64_t FullStackId =
188+
mdconst::dyn_extract<ConstantInt>(ContextSizePair->getOperand(0))
189+
->getZExtValue();
190+
uint64_t TotalSize =
191+
mdconst::dyn_extract<ConstantInt>(ContextSizePair->getOperand(1))
192+
->getZExtValue();
193+
ContextSizeInfo.push_back({FullStackId, TotalSize});
194+
}
195+
}
196+
addCallStack(getMIBAllocType(MIB), CallStack, std::move(ContextSizeInfo));
186197
}
187198

188199
static MDNode *createMIBNode(LLVMContext &Ctx, ArrayRef<uint64_t> MIBCallStack,
189-
AllocationType AllocType, uint64_t TotalSize) {
200+
AllocationType AllocType,
201+
ArrayRef<ContextTotalSize> ContextSizeInfo) {
190202
SmallVector<Metadata *> MIBPayload(
191203
{buildCallstackMetadata(MIBCallStack, Ctx)});
192204
MIBPayload.push_back(
193205
MDString::get(Ctx, getAllocTypeAttributeString(AllocType)));
194-
if (TotalSize)
195-
MIBPayload.push_back(ValueAsMetadata::get(
196-
ConstantInt::get(Type::getInt64Ty(Ctx), TotalSize)));
206+
if (!ContextSizeInfo.empty()) {
207+
for (const auto &[FullStackId, TotalSize] : ContextSizeInfo) {
208+
auto *FullStackIdMD = ValueAsMetadata::get(
209+
ConstantInt::get(Type::getInt64Ty(Ctx), FullStackId));
210+
auto *TotalSizeMD = ValueAsMetadata::get(
211+
ConstantInt::get(Type::getInt64Ty(Ctx), TotalSize));
212+
auto *ContextSizeMD = MDNode::get(Ctx, {FullStackIdMD, TotalSizeMD});
213+
MIBPayload.push_back(ContextSizeMD);
214+
}
215+
}
197216
return MDNode::get(Ctx, MIBPayload);
198217
}
199218

219+
void CallStackTrie::collectContextSizeInfo(
220+
CallStackTrieNode *Node, std::vector<ContextTotalSize> &ContextSizeInfo) {
221+
ContextSizeInfo.insert(ContextSizeInfo.end(), Node->ContextSizeInfo.begin(),
222+
Node->ContextSizeInfo.end());
223+
for (auto &Caller : Node->Callers)
224+
collectContextSizeInfo(Caller.second, ContextSizeInfo);
225+
}
226+
200227
// Recursive helper to trim contexts and create metadata nodes.
201228
// Caller should have pushed Node's loc to MIBCallStack. Doing this in the
202229
// caller makes it simpler to handle the many early returns in this method.
@@ -207,8 +234,10 @@ bool CallStackTrie::buildMIBNodes(CallStackTrieNode *Node, LLVMContext &Ctx,
207234
// Trim context below the first node in a prefix with a single alloc type.
208235
// Add an MIB record for the current call stack prefix.
209236
if (hasSingleAllocType(Node->AllocTypes)) {
237+
std::vector<ContextTotalSize> ContextSizeInfo;
238+
collectContextSizeInfo(Node, ContextSizeInfo);
210239
MIBNodes.push_back(createMIBNode(
211-
Ctx, MIBCallStack, (AllocationType)Node->AllocTypes, Node->TotalSize));
240+
Ctx, MIBCallStack, (AllocationType)Node->AllocTypes, ContextSizeInfo));
212241
return true;
213242
}
214243

@@ -244,8 +273,10 @@ bool CallStackTrie::buildMIBNodes(CallStackTrieNode *Node, LLVMContext &Ctx,
244273
// non-cold allocation type.
245274
if (!CalleeHasAmbiguousCallerContext)
246275
return false;
276+
std::vector<ContextTotalSize> ContextSizeInfo;
277+
collectContextSizeInfo(Node, ContextSizeInfo);
247278
MIBNodes.push_back(createMIBNode(Ctx, MIBCallStack, AllocationType::NotCold,
248-
Node->TotalSize));
279+
ContextSizeInfo));
249280
return true;
250281
}
251282

@@ -257,11 +288,15 @@ bool CallStackTrie::buildAndAttachMIBMetadata(CallBase *CI) {
257288
if (hasSingleAllocType(Alloc->AllocTypes)) {
258289
addAllocTypeAttribute(Ctx, CI, (AllocationType)Alloc->AllocTypes);
259290
if (MemProfReportHintedSizes) {
260-
assert(Alloc->TotalSize);
261-
errs() << "Total size for allocation with location hash " << AllocStackId
262-
<< " and single alloc type "
263-
<< getAllocTypeAttributeString((AllocationType)Alloc->AllocTypes)
264-
<< ": " << Alloc->TotalSize << "\n";
291+
std::vector<ContextTotalSize> ContextSizeInfo;
292+
collectContextSizeInfo(Alloc, ContextSizeInfo);
293+
for (const auto &[FullStackId, TotalSize] : ContextSizeInfo) {
294+
errs()
295+
<< "MemProf hinting: Total size for full allocation context hash "
296+
<< FullStackId << " and single alloc type "
297+
<< getAllocTypeAttributeString((AllocationType)Alloc->AllocTypes)
298+
<< ": " << TotalSize << "\n";
299+
}
265300
}
266301
return false;
267302
}

llvm/lib/Analysis/ModuleSummaryAnalysis.cpp

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ static void computeFunctionSummary(
522522
if (MemProfMD) {
523523
std::vector<MIBInfo> MIBs;
524524
std::vector<uint64_t> TotalSizes;
525+
std::vector<std::vector<ContextTotalSize>> ContextSizeInfos;
525526
for (auto &MDOp : MemProfMD->operands()) {
526527
auto *MIBMD = cast<const MDNode>(MDOp);
527528
MDNode *StackNode = getMIBStackNode(MIBMD);
@@ -539,18 +540,32 @@ static void computeFunctionSummary(
539540
if (StackIdIndices.empty() || StackIdIndices.back() != StackIdIdx)
540541
StackIdIndices.push_back(StackIdIdx);
541542
}
543+
// If we have context size information, collect it for inclusion in
544+
// the summary.
545+
assert(MIBMD->getNumOperands() > 2 || !MemProfReportHintedSizes);
546+
if (MIBMD->getNumOperands() > 2) {
547+
std::vector<ContextTotalSize> ContextSizes;
548+
for (unsigned I = 2; I < MIBMD->getNumOperands(); I++) {
549+
MDNode *ContextSizePair = dyn_cast<MDNode>(MIBMD->getOperand(I));
550+
assert(ContextSizePair->getNumOperands() == 2);
551+
uint64_t FullStackId = mdconst::dyn_extract<ConstantInt>(
552+
ContextSizePair->getOperand(0))
553+
->getZExtValue();
554+
uint64_t TS = mdconst::dyn_extract<ConstantInt>(
555+
ContextSizePair->getOperand(1))
556+
->getZExtValue();
557+
ContextSizes.push_back({FullStackId, TS});
558+
}
559+
ContextSizeInfos.push_back(std::move(ContextSizes));
560+
}
542561
MIBs.push_back(
543562
MIBInfo(getMIBAllocType(MIBMD), std::move(StackIdIndices)));
544-
if (MemProfReportHintedSizes) {
545-
auto TotalSize = getMIBTotalSize(MIBMD);
546-
assert(TotalSize);
547-
TotalSizes.push_back(TotalSize);
548-
}
549563
}
550564
Allocs.push_back(AllocInfo(std::move(MIBs)));
551-
if (MemProfReportHintedSizes) {
552-
assert(Allocs.back().MIBs.size() == TotalSizes.size());
553-
Allocs.back().TotalSizes = std::move(TotalSizes);
565+
assert(!ContextSizeInfos.empty() || !MemProfReportHintedSizes);
566+
if (!ContextSizeInfos.empty()) {
567+
assert(Allocs.back().MIBs.size() == ContextSizeInfos.size());
568+
Allocs.back().ContextSizeInfos = std::move(ContextSizeInfos);
554569
}
555570
} else if (!InstCallsite.empty()) {
556571
SmallVector<unsigned> StackIdIndices;

llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ GetCodeName(unsigned CodeID, unsigned BlockID,
328328
STRINGIFY_CODE(FS, COMBINED_CALLSITE_INFO)
329329
STRINGIFY_CODE(FS, COMBINED_ALLOC_INFO)
330330
STRINGIFY_CODE(FS, STACK_IDS)
331+
STRINGIFY_CODE(FS, ALLOC_CONTEXT_IDS)
331332
}
332333
case bitc::METADATA_ATTACHMENT_ID:
333334
switch (CodeID) {

0 commit comments

Comments
 (0)