2022-02-17 16:01:31 -08:00
|
|
|
#include "llvm/ProfileData/MemProf.h"
|
2022-03-22 14:40:02 -07:00
|
|
|
#include "llvm/ADT/SmallVector.h"
|
|
|
|
#include "llvm/IR/Function.h"
|
2022-02-23 22:18:20 +01:00
|
|
|
#include "llvm/ProfileData/InstrProf.h"
|
2023-08-29 18:58:39 +00:00
|
|
|
#include "llvm/ProfileData/SampleProf.h"
|
2024-03-23 19:50:15 -07:00
|
|
|
#include "llvm/Support/BLAKE3.h"
|
2022-02-17 16:01:31 -08:00
|
|
|
#include "llvm/Support/Endian.h"
|
|
|
|
#include "llvm/Support/EndianStream.h"
|
2024-03-23 19:50:15 -07:00
|
|
|
#include "llvm/Support/HashBuilder.h"
|
2022-02-17 16:01:31 -08:00
|
|
|
|
|
|
|
namespace llvm {
|
|
|
|
namespace memprof {
|
2024-04-25 12:12:28 -07:00
|
|
|
MemProfSchema getFullSchema() {
|
|
|
|
MemProfSchema List;
|
|
|
|
#define MIBEntryDef(NameTag, Name, Type) List.push_back(Meta::Name);
|
|
|
|
#include "llvm/ProfileData/MIBEntryDef.inc"
|
|
|
|
#undef MIBEntryDef
|
|
|
|
return List;
|
|
|
|
}
|
|
|
|
|
|
|
|
MemProfSchema getHotColdSchema() {
|
|
|
|
return {Meta::AllocCount, Meta::TotalSize, Meta::TotalLifetime,
|
|
|
|
Meta::TotalLifetimeAccessDensity};
|
|
|
|
}
|
|
|
|
|
2024-04-23 13:44:31 -07:00
|
|
|
static size_t serializedSizeV0(const IndexedAllocationInfo &IAI,
|
|
|
|
const MemProfSchema &Schema) {
|
2024-04-03 21:48:38 -07:00
|
|
|
size_t Size = 0;
|
|
|
|
// The number of frames to serialize.
|
|
|
|
Size += sizeof(uint64_t);
|
|
|
|
// The callstack frame ids.
|
|
|
|
Size += sizeof(FrameId) * IAI.CallStack.size();
|
|
|
|
// The size of the payload.
|
2024-04-23 13:44:31 -07:00
|
|
|
Size += PortableMemInfoBlock::serializedSize(Schema);
|
2024-04-03 21:48:38 -07:00
|
|
|
return Size;
|
|
|
|
}
|
2022-02-17 16:01:31 -08:00
|
|
|
|
2024-04-23 13:44:31 -07:00
|
|
|
static size_t serializedSizeV2(const IndexedAllocationInfo &IAI,
|
|
|
|
const MemProfSchema &Schema) {
|
2024-04-03 21:48:38 -07:00
|
|
|
size_t Size = 0;
|
|
|
|
// The CallStackId
|
|
|
|
Size += sizeof(CallStackId);
|
|
|
|
// The size of the payload.
|
2024-04-23 13:44:31 -07:00
|
|
|
Size += PortableMemInfoBlock::serializedSize(Schema);
|
2024-04-03 21:48:38 -07:00
|
|
|
return Size;
|
|
|
|
}
|
|
|
|
|
2024-05-31 10:29:14 -07:00
|
|
|
static size_t serializedSizeV3(const IndexedAllocationInfo &IAI,
|
|
|
|
const MemProfSchema &Schema) {
|
|
|
|
size_t Size = 0;
|
|
|
|
// The linear call stack ID.
|
|
|
|
Size += sizeof(LinearCallStackId);
|
|
|
|
// The size of the payload.
|
|
|
|
Size += PortableMemInfoBlock::serializedSize(Schema);
|
|
|
|
return Size;
|
|
|
|
}
|
|
|
|
|
2024-04-23 13:44:31 -07:00
|
|
|
size_t IndexedAllocationInfo::serializedSize(const MemProfSchema &Schema,
|
|
|
|
IndexedVersion Version) const {
|
2024-04-03 21:48:38 -07:00
|
|
|
switch (Version) {
|
|
|
|
case Version0:
|
|
|
|
case Version1:
|
2024-04-23 13:44:31 -07:00
|
|
|
return serializedSizeV0(*this, Schema);
|
2024-04-03 21:48:38 -07:00
|
|
|
case Version2:
|
2024-04-23 13:44:31 -07:00
|
|
|
return serializedSizeV2(*this, Schema);
|
2024-05-31 10:29:14 -07:00
|
|
|
case Version3:
|
|
|
|
return serializedSizeV3(*this, Schema);
|
2024-04-03 21:48:38 -07:00
|
|
|
}
|
|
|
|
llvm_unreachable("unsupported MemProf version");
|
|
|
|
}
|
|
|
|
|
2024-04-23 13:44:31 -07:00
|
|
|
static size_t serializedSizeV0(const IndexedMemProfRecord &Record,
|
|
|
|
const MemProfSchema &Schema) {
|
2024-04-15 10:00:56 -07:00
|
|
|
// The number of alloc sites to serialize.
|
|
|
|
size_t Result = sizeof(uint64_t);
|
2024-04-03 21:48:38 -07:00
|
|
|
for (const IndexedAllocationInfo &N : Record.AllocSites)
|
2024-04-23 13:44:31 -07:00
|
|
|
Result += N.serializedSize(Schema, Version0);
|
2024-04-03 21:48:38 -07:00
|
|
|
|
|
|
|
// The number of callsites we have information for.
|
|
|
|
Result += sizeof(uint64_t);
|
|
|
|
for (const auto &Frames : Record.CallSites) {
|
|
|
|
// The number of frame ids to serialize.
|
|
|
|
Result += sizeof(uint64_t);
|
|
|
|
Result += Frames.size() * sizeof(FrameId);
|
|
|
|
}
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
2024-04-23 13:44:31 -07:00
|
|
|
static size_t serializedSizeV2(const IndexedMemProfRecord &Record,
|
|
|
|
const MemProfSchema &Schema) {
|
2024-04-15 10:00:56 -07:00
|
|
|
// The number of alloc sites to serialize.
|
|
|
|
size_t Result = sizeof(uint64_t);
|
2024-04-03 21:48:38 -07:00
|
|
|
for (const IndexedAllocationInfo &N : Record.AllocSites)
|
2024-04-23 13:44:31 -07:00
|
|
|
Result += N.serializedSize(Schema, Version2);
|
2024-04-03 21:48:38 -07:00
|
|
|
|
|
|
|
// The number of callsites we have information for.
|
|
|
|
Result += sizeof(uint64_t);
|
|
|
|
// The CallStackId
|
|
|
|
Result += Record.CallSiteIds.size() * sizeof(CallStackId);
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
2024-05-31 10:29:14 -07:00
|
|
|
static size_t serializedSizeV3(const IndexedMemProfRecord &Record,
|
|
|
|
const MemProfSchema &Schema) {
|
|
|
|
// The number of alloc sites to serialize.
|
|
|
|
size_t Result = sizeof(uint64_t);
|
|
|
|
for (const IndexedAllocationInfo &N : Record.AllocSites)
|
|
|
|
Result += N.serializedSize(Schema, Version3);
|
|
|
|
|
|
|
|
// The number of callsites we have information for.
|
|
|
|
Result += sizeof(uint64_t);
|
|
|
|
// The linear call stack ID.
|
|
|
|
Result += Record.CallSiteIds.size() * sizeof(LinearCallStackId);
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
2024-04-23 13:44:31 -07:00
|
|
|
size_t IndexedMemProfRecord::serializedSize(const MemProfSchema &Schema,
|
|
|
|
IndexedVersion Version) const {
|
2024-04-03 21:48:38 -07:00
|
|
|
switch (Version) {
|
|
|
|
case Version0:
|
|
|
|
case Version1:
|
2024-04-23 13:44:31 -07:00
|
|
|
return serializedSizeV0(*this, Schema);
|
2024-04-03 21:48:38 -07:00
|
|
|
case Version2:
|
2024-04-23 13:44:31 -07:00
|
|
|
return serializedSizeV2(*this, Schema);
|
2024-05-31 10:29:14 -07:00
|
|
|
case Version3:
|
|
|
|
return serializedSizeV3(*this, Schema);
|
2024-04-03 21:48:38 -07:00
|
|
|
}
|
|
|
|
llvm_unreachable("unsupported MemProf version");
|
|
|
|
}
|
|
|
|
|
2024-04-07 11:38:15 -07:00
|
|
|
static void serializeV0(const IndexedMemProfRecord &Record,
|
|
|
|
const MemProfSchema &Schema, raw_ostream &OS) {
|
2022-02-17 16:01:31 -08:00
|
|
|
using namespace support;
|
|
|
|
|
2023-10-13 23:16:25 -07:00
|
|
|
endian::Writer LE(OS, llvm::endianness::little);
|
2022-02-17 16:01:31 -08:00
|
|
|
|
2024-04-03 21:48:38 -07:00
|
|
|
LE.write<uint64_t>(Record.AllocSites.size());
|
|
|
|
for (const IndexedAllocationInfo &N : Record.AllocSites) {
|
2022-03-22 14:40:02 -07:00
|
|
|
LE.write<uint64_t>(N.CallStack.size());
|
2022-03-21 19:39:24 -07:00
|
|
|
for (const FrameId &Id : N.CallStack)
|
|
|
|
LE.write<FrameId>(Id);
|
2022-03-22 14:40:02 -07:00
|
|
|
N.Info.serialize(Schema, OS);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Related contexts.
|
2024-04-03 21:48:38 -07:00
|
|
|
LE.write<uint64_t>(Record.CallSites.size());
|
|
|
|
for (const auto &Frames : Record.CallSites) {
|
2022-03-22 14:40:02 -07:00
|
|
|
LE.write<uint64_t>(Frames.size());
|
2022-03-21 19:39:24 -07:00
|
|
|
for (const FrameId &Id : Frames)
|
|
|
|
LE.write<FrameId>(Id);
|
2022-02-17 16:01:31 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-07 11:38:15 -07:00
|
|
|
static void serializeV2(const IndexedMemProfRecord &Record,
|
|
|
|
const MemProfSchema &Schema, raw_ostream &OS) {
|
2024-04-03 21:48:38 -07:00
|
|
|
using namespace support;
|
|
|
|
|
|
|
|
endian::Writer LE(OS, llvm::endianness::little);
|
|
|
|
|
|
|
|
LE.write<uint64_t>(Record.AllocSites.size());
|
|
|
|
for (const IndexedAllocationInfo &N : Record.AllocSites) {
|
|
|
|
LE.write<CallStackId>(N.CSId);
|
|
|
|
N.Info.serialize(Schema, OS);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Related contexts.
|
|
|
|
LE.write<uint64_t>(Record.CallSiteIds.size());
|
|
|
|
for (const auto &CSId : Record.CallSiteIds)
|
|
|
|
LE.write<CallStackId>(CSId);
|
|
|
|
}
|
|
|
|
|
2024-05-31 14:41:05 -07:00
|
|
|
static void serializeV3(
|
|
|
|
const IndexedMemProfRecord &Record, const MemProfSchema &Schema,
|
|
|
|
raw_ostream &OS,
|
|
|
|
llvm::DenseMap<CallStackId, LinearCallStackId> &MemProfCallStackIndexes) {
|
2024-05-30 14:28:22 -07:00
|
|
|
using namespace support;
|
|
|
|
|
|
|
|
endian::Writer LE(OS, llvm::endianness::little);
|
|
|
|
|
|
|
|
LE.write<uint64_t>(Record.AllocSites.size());
|
|
|
|
for (const IndexedAllocationInfo &N : Record.AllocSites) {
|
|
|
|
assert(MemProfCallStackIndexes.contains(N.CSId));
|
2024-05-31 14:41:05 -07:00
|
|
|
LE.write<LinearCallStackId>(MemProfCallStackIndexes[N.CSId]);
|
2024-05-30 14:28:22 -07:00
|
|
|
N.Info.serialize(Schema, OS);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Related contexts.
|
|
|
|
LE.write<uint64_t>(Record.CallSiteIds.size());
|
|
|
|
for (const auto &CSId : Record.CallSiteIds) {
|
|
|
|
assert(MemProfCallStackIndexes.contains(CSId));
|
2024-05-31 14:41:05 -07:00
|
|
|
LE.write<LinearCallStackId>(MemProfCallStackIndexes[CSId]);
|
2024-05-30 14:28:22 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IndexedMemProfRecord::serialize(
|
|
|
|
const MemProfSchema &Schema, raw_ostream &OS, IndexedVersion Version,
|
2024-06-07 15:06:04 -07:00
|
|
|
llvm::DenseMap<CallStackId, LinearCallStackId> *MemProfCallStackIndexes)
|
|
|
|
const {
|
2024-04-03 21:48:38 -07:00
|
|
|
switch (Version) {
|
|
|
|
case Version0:
|
|
|
|
case Version1:
|
|
|
|
serializeV0(*this, Schema, OS);
|
|
|
|
return;
|
|
|
|
case Version2:
|
|
|
|
serializeV2(*this, Schema, OS);
|
|
|
|
return;
|
2024-05-30 14:28:22 -07:00
|
|
|
case Version3:
|
|
|
|
serializeV3(*this, Schema, OS, *MemProfCallStackIndexes);
|
|
|
|
return;
|
2024-04-03 21:48:38 -07:00
|
|
|
}
|
|
|
|
llvm_unreachable("unsupported MemProf version");
|
|
|
|
}
|
|
|
|
|
2024-04-07 11:38:15 -07:00
|
|
|
static IndexedMemProfRecord deserializeV0(const MemProfSchema &Schema,
|
|
|
|
const unsigned char *Ptr) {
|
2022-02-17 16:01:31 -08:00
|
|
|
using namespace support;
|
|
|
|
|
2022-03-21 19:39:24 -07:00
|
|
|
IndexedMemProfRecord Record;
|
2022-03-22 14:40:02 -07:00
|
|
|
|
|
|
|
// Read the meminfo nodes.
|
2023-10-13 23:16:25 -07:00
|
|
|
const uint64_t NumNodes =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
|
2022-03-22 14:40:02 -07:00
|
|
|
for (uint64_t I = 0; I < NumNodes; I++) {
|
2022-03-21 19:39:24 -07:00
|
|
|
IndexedAllocationInfo Node;
|
2022-02-17 16:01:31 -08:00
|
|
|
const uint64_t NumFrames =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
|
2022-02-17 16:01:31 -08:00
|
|
|
for (uint64_t J = 0; J < NumFrames; J++) {
|
2023-10-13 23:16:25 -07:00
|
|
|
const FrameId Id =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<FrameId, llvm::endianness::little>(Ptr);
|
2022-03-21 19:39:24 -07:00
|
|
|
Node.CallStack.push_back(Id);
|
2022-02-17 16:01:31 -08:00
|
|
|
}
|
2024-03-25 14:21:49 -07:00
|
|
|
Node.CSId = hashCallStack(Node.CallStack);
|
2022-03-22 14:40:02 -07:00
|
|
|
Node.Info.deserialize(Schema, Ptr);
|
2024-04-23 13:44:31 -07:00
|
|
|
Ptr += PortableMemInfoBlock::serializedSize(Schema);
|
2022-03-22 14:40:02 -07:00
|
|
|
Record.AllocSites.push_back(Node);
|
2022-02-17 16:01:31 -08:00
|
|
|
}
|
2022-03-22 14:40:02 -07:00
|
|
|
|
|
|
|
// Read the callsite information.
|
2023-10-13 23:16:25 -07:00
|
|
|
const uint64_t NumCtxs =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
|
2022-03-22 14:40:02 -07:00
|
|
|
for (uint64_t J = 0; J < NumCtxs; J++) {
|
|
|
|
const uint64_t NumFrames =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
|
2022-03-21 19:39:24 -07:00
|
|
|
llvm::SmallVector<FrameId> Frames;
|
|
|
|
Frames.reserve(NumFrames);
|
2022-03-22 14:40:02 -07:00
|
|
|
for (uint64_t K = 0; K < NumFrames; K++) {
|
2023-10-13 23:16:25 -07:00
|
|
|
const FrameId Id =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<FrameId, llvm::endianness::little>(Ptr);
|
2022-03-21 19:39:24 -07:00
|
|
|
Frames.push_back(Id);
|
2022-03-22 14:40:02 -07:00
|
|
|
}
|
|
|
|
Record.CallSites.push_back(Frames);
|
2024-04-03 21:48:38 -07:00
|
|
|
Record.CallSiteIds.push_back(hashCallStack(Frames));
|
|
|
|
}
|
|
|
|
|
|
|
|
return Record;
|
|
|
|
}
|
|
|
|
|
2024-04-07 11:38:15 -07:00
|
|
|
static IndexedMemProfRecord deserializeV2(const MemProfSchema &Schema,
|
|
|
|
const unsigned char *Ptr) {
|
2024-04-03 21:48:38 -07:00
|
|
|
using namespace support;
|
|
|
|
|
|
|
|
IndexedMemProfRecord Record;
|
|
|
|
|
|
|
|
// Read the meminfo nodes.
|
|
|
|
const uint64_t NumNodes =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
|
2024-05-24 10:42:46 -07:00
|
|
|
Record.AllocSites.reserve(NumNodes);
|
2024-04-03 21:48:38 -07:00
|
|
|
for (uint64_t I = 0; I < NumNodes; I++) {
|
|
|
|
IndexedAllocationInfo Node;
|
2024-04-16 12:47:02 -07:00
|
|
|
Node.CSId = endian::readNext<CallStackId, llvm::endianness::little>(Ptr);
|
2024-04-03 21:48:38 -07:00
|
|
|
Node.Info.deserialize(Schema, Ptr);
|
2024-04-23 13:44:31 -07:00
|
|
|
Ptr += PortableMemInfoBlock::serializedSize(Schema);
|
2024-04-03 21:48:38 -07:00
|
|
|
Record.AllocSites.push_back(Node);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the callsite information.
|
|
|
|
const uint64_t NumCtxs =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
|
2024-05-24 10:42:46 -07:00
|
|
|
Record.CallSiteIds.reserve(NumCtxs);
|
2024-04-03 21:48:38 -07:00
|
|
|
for (uint64_t J = 0; J < NumCtxs; J++) {
|
|
|
|
CallStackId CSId =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<CallStackId, llvm::endianness::little>(Ptr);
|
2024-04-03 21:48:38 -07:00
|
|
|
Record.CallSiteIds.push_back(CSId);
|
2022-03-22 14:40:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return Record;
|
|
|
|
}
|
2024-04-03 21:48:38 -07:00
|
|
|
|
2024-05-31 10:29:14 -07:00
|
|
|
static IndexedMemProfRecord deserializeV3(const MemProfSchema &Schema,
|
|
|
|
const unsigned char *Ptr) {
|
|
|
|
using namespace support;
|
|
|
|
|
|
|
|
IndexedMemProfRecord Record;
|
|
|
|
|
|
|
|
// Read the meminfo nodes.
|
|
|
|
const uint64_t NumNodes =
|
|
|
|
endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
|
|
|
|
Record.AllocSites.reserve(NumNodes);
|
|
|
|
for (uint64_t I = 0; I < NumNodes; I++) {
|
|
|
|
IndexedAllocationInfo Node;
|
2024-05-31 14:41:05 -07:00
|
|
|
Node.CSId =
|
|
|
|
endian::readNext<LinearCallStackId, llvm::endianness::little>(Ptr);
|
2024-05-31 10:29:14 -07:00
|
|
|
Node.Info.deserialize(Schema, Ptr);
|
|
|
|
Ptr += PortableMemInfoBlock::serializedSize(Schema);
|
|
|
|
Record.AllocSites.push_back(Node);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the callsite information.
|
|
|
|
const uint64_t NumCtxs =
|
|
|
|
endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
|
|
|
|
Record.CallSiteIds.reserve(NumCtxs);
|
|
|
|
for (uint64_t J = 0; J < NumCtxs; J++) {
|
|
|
|
// We are storing LinearCallStackId in CallSiteIds, which is a vector of
|
|
|
|
// CallStackId. Assert that CallStackId is no smaller than
|
|
|
|
// LinearCallStackId.
|
|
|
|
static_assert(sizeof(LinearCallStackId) <= sizeof(CallStackId));
|
|
|
|
LinearCallStackId CSId =
|
|
|
|
endian::readNext<LinearCallStackId, llvm::endianness::little>(Ptr);
|
|
|
|
Record.CallSiteIds.push_back(CSId);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Record;
|
|
|
|
}
|
|
|
|
|
2024-04-03 21:48:38 -07:00
|
|
|
IndexedMemProfRecord
|
|
|
|
IndexedMemProfRecord::deserialize(const MemProfSchema &Schema,
|
|
|
|
const unsigned char *Ptr,
|
|
|
|
IndexedVersion Version) {
|
|
|
|
switch (Version) {
|
|
|
|
case Version0:
|
|
|
|
case Version1:
|
|
|
|
return deserializeV0(Schema, Ptr);
|
|
|
|
case Version2:
|
|
|
|
return deserializeV2(Schema, Ptr);
|
2024-05-31 10:29:14 -07:00
|
|
|
case Version3:
|
|
|
|
return deserializeV3(Schema, Ptr);
|
2024-04-03 21:48:38 -07:00
|
|
|
}
|
|
|
|
llvm_unreachable("unsupported MemProf version");
|
|
|
|
}
|
2022-03-22 14:40:02 -07:00
|
|
|
|
2024-04-16 10:16:48 -07:00
|
|
|
MemProfRecord IndexedMemProfRecord::toMemProfRecord(
|
2024-06-06 14:24:43 -07:00
|
|
|
llvm::function_ref<std::vector<Frame>(const CallStackId)> Callback) const {
|
2024-04-16 10:16:48 -07:00
|
|
|
MemProfRecord Record;
|
|
|
|
|
2024-05-23 12:27:38 -07:00
|
|
|
Record.AllocSites.reserve(AllocSites.size());
|
2024-06-07 18:32:58 -07:00
|
|
|
for (const IndexedAllocationInfo &IndexedAI : AllocSites) {
|
|
|
|
AllocationInfo AI;
|
2024-04-16 10:16:48 -07:00
|
|
|
AI.Info = IndexedAI.Info;
|
|
|
|
AI.CallStack = Callback(IndexedAI.CSId);
|
2024-05-23 12:27:38 -07:00
|
|
|
Record.AllocSites.push_back(std::move(AI));
|
2024-04-16 10:16:48 -07:00
|
|
|
}
|
|
|
|
|
2024-05-23 12:27:38 -07:00
|
|
|
Record.CallSites.reserve(CallSiteIds.size());
|
2024-06-07 18:32:58 -07:00
|
|
|
for (CallStackId CSId : CallSiteIds)
|
2024-04-16 10:16:48 -07:00
|
|
|
Record.CallSites.push_back(Callback(CSId));
|
|
|
|
|
|
|
|
return Record;
|
|
|
|
}
|
|
|
|
|
2022-03-21 19:39:24 -07:00
|
|
|
GlobalValue::GUID IndexedMemProfRecord::getGUID(const StringRef FunctionName) {
|
2023-11-29 10:34:21 -08:00
|
|
|
// Canonicalize the function name to drop suffixes such as ".llvm.". Note
|
|
|
|
// we do not drop any ".__uniq." suffixes, as getCanonicalFnName does not drop
|
|
|
|
// those by default. This is by design to differentiate internal linkage
|
|
|
|
// functions during matching. By dropping the other suffixes we can then match
|
|
|
|
// functions in the profile use phase prior to their addition. Note that this
|
|
|
|
// applies to both instrumented and sampled function names.
|
2023-08-29 18:58:39 +00:00
|
|
|
StringRef CanonicalName =
|
|
|
|
sampleprof::FunctionSamples::getCanonicalFnName(FunctionName);
|
2022-03-22 14:40:02 -07:00
|
|
|
|
|
|
|
// We use the function guid which we expect to be a uint64_t. At
|
2023-08-29 18:58:39 +00:00
|
|
|
// this time, it is the lower 64 bits of the md5 of the canonical
|
|
|
|
// function name.
|
|
|
|
return Function::getGUID(CanonicalName);
|
2022-02-17 16:01:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
Expected<MemProfSchema> readMemProfSchema(const unsigned char *&Buffer) {
|
|
|
|
using namespace support;
|
|
|
|
|
|
|
|
const unsigned char *Ptr = Buffer;
|
|
|
|
const uint64_t NumSchemaIds =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
|
2022-02-17 16:01:31 -08:00
|
|
|
if (NumSchemaIds > static_cast<uint64_t>(Meta::Size)) {
|
|
|
|
return make_error<InstrProfError>(instrprof_error::malformed,
|
|
|
|
"memprof schema invalid");
|
|
|
|
}
|
|
|
|
|
|
|
|
MemProfSchema Result;
|
|
|
|
for (size_t I = 0; I < NumSchemaIds; I++) {
|
2023-10-13 23:16:25 -07:00
|
|
|
const uint64_t Tag =
|
2024-04-16 12:47:02 -07:00
|
|
|
endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
|
2022-02-17 16:01:31 -08:00
|
|
|
if (Tag >= static_cast<uint64_t>(Meta::Size)) {
|
|
|
|
return make_error<InstrProfError>(instrprof_error::malformed,
|
|
|
|
"memprof schema invalid");
|
|
|
|
}
|
|
|
|
Result.push_back(static_cast<Meta>(Tag));
|
|
|
|
}
|
2024-06-10 16:38:24 -07:00
|
|
|
// Advance the buffer to one past the schema if we succeeded.
|
2022-02-17 16:01:31 -08:00
|
|
|
Buffer = Ptr;
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
2024-03-23 19:50:15 -07:00
|
|
|
CallStackId hashCallStack(ArrayRef<FrameId> CS) {
|
|
|
|
llvm::HashBuilder<llvm::TruncatedBLAKE3<8>, llvm::endianness::little>
|
|
|
|
HashBuilder;
|
|
|
|
for (FrameId F : CS)
|
|
|
|
HashBuilder.add(F);
|
|
|
|
llvm::BLAKE3Result<8> Hash = HashBuilder.final();
|
|
|
|
CallStackId CSId;
|
|
|
|
std::memcpy(&CSId, Hash.data(), sizeof(Hash));
|
|
|
|
return CSId;
|
|
|
|
}
|
|
|
|
|
[memprof] Add CallStackRadixTreeBuilder (#93784)
Call stacks are a huge portion of the MemProf profile, taking up 70+%
of the profile file size.
This patch implements a radix tree to compress call stacks, which are
known to have long common prefixes. Specifically,
CallStackRadixTreeBuilder, introduced in this patch, takes call stacks
in the MemProf profile, sorts them in the dictionary order to maximize
the common prefix between adjacent call stacks, and then encodes a
radix tree into a single array that is ready for serialization.
The resulting radix array is essentially a concatenation of call stack
arrays, each encoded with its length followed by the payload, except
that these arrays contain "instructions" like "skip 7 elements
forward" to borrow common prefixes from other call stacks.
This patch does not integrate with the MemProf
serialization/deserialization infrastructure yet. Once integrated,
the radix tree is expected to roughly halve the file size of the
MemProf profile.
2024-06-06 15:52:45 -07:00
|
|
|
// Encode a call stack into RadixArray. Return the starting index within
|
|
|
|
// RadixArray. For each call stack we encode, we emit two or three components
|
|
|
|
// into RadixArray. If a given call stack doesn't have a common prefix relative
|
|
|
|
// to the previous one, we emit:
|
|
|
|
//
|
|
|
|
// - the frames in the given call stack in the root-to-leaf order
|
|
|
|
//
|
|
|
|
// - the length of the given call stack
|
|
|
|
//
|
|
|
|
// If a given call stack has a non-empty common prefix relative to the previous
|
|
|
|
// one, we emit:
|
|
|
|
//
|
|
|
|
// - the relative location of the common prefix, encoded as a negative number.
|
|
|
|
//
|
|
|
|
// - a portion of the given call stack that's beyond the common prefix
|
|
|
|
//
|
|
|
|
// - the length of the given call stack, including the length of the common
|
|
|
|
// prefix.
|
|
|
|
//
|
|
|
|
// The resulting RadixArray requires a somewhat unintuitive backward traversal
|
|
|
|
// to reconstruct a call stack -- read the call stack length and scan backward
|
|
|
|
// while collecting frames in the leaf to root order. build, the caller of this
|
|
|
|
// function, reverses RadixArray in place so that we can reconstruct a call
|
|
|
|
// stack as if we were deserializing an array in a typical way -- the call stack
|
|
|
|
// length followed by the frames in the leaf-to-root order except that we need
|
|
|
|
// to handle pointers to parents along the way.
|
|
|
|
//
|
|
|
|
// To quickly determine the location of the common prefix within RadixArray,
|
|
|
|
// Indexes caches the indexes of the previous call stack's frames within
|
|
|
|
// RadixArray.
|
|
|
|
LinearCallStackId CallStackRadixTreeBuilder::encodeCallStack(
|
|
|
|
const llvm::SmallVector<FrameId> *CallStack,
|
|
|
|
const llvm::SmallVector<FrameId> *Prev,
|
|
|
|
const llvm::DenseMap<FrameId, LinearFrameId> &MemProfFrameIndexes) {
|
|
|
|
// Compute the length of the common root prefix between Prev and CallStack.
|
|
|
|
uint32_t CommonLen = 0;
|
|
|
|
if (Prev) {
|
|
|
|
auto Pos = std::mismatch(Prev->rbegin(), Prev->rend(), CallStack->rbegin(),
|
|
|
|
CallStack->rend());
|
|
|
|
CommonLen = std::distance(CallStack->rbegin(), Pos.second);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Drop the portion beyond CommonLen.
|
|
|
|
assert(CommonLen <= Indexes.size());
|
|
|
|
Indexes.resize(CommonLen);
|
|
|
|
|
|
|
|
// Append a pointer to the parent.
|
|
|
|
if (CommonLen) {
|
|
|
|
uint32_t CurrentIndex = RadixArray.size();
|
|
|
|
uint32_t ParentIndex = Indexes.back();
|
|
|
|
// The offset to the parent must be negative because we are pointing to an
|
|
|
|
// element we've already added to RadixArray.
|
|
|
|
assert(ParentIndex < CurrentIndex);
|
|
|
|
RadixArray.push_back(ParentIndex - CurrentIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the part of the call stack beyond the common prefix to RadixArray.
|
|
|
|
assert(CommonLen <= CallStack->size());
|
|
|
|
for (FrameId F : llvm::drop_begin(llvm::reverse(*CallStack), CommonLen)) {
|
|
|
|
// Remember the index of F in RadixArray.
|
|
|
|
Indexes.push_back(RadixArray.size());
|
|
|
|
RadixArray.push_back(MemProfFrameIndexes.find(F)->second);
|
|
|
|
}
|
|
|
|
assert(CallStack->size() == Indexes.size());
|
|
|
|
|
|
|
|
// End with the call stack length.
|
|
|
|
RadixArray.push_back(CallStack->size());
|
|
|
|
|
|
|
|
// Return the index within RadixArray where we can start reconstructing a
|
|
|
|
// given call stack from.
|
|
|
|
return RadixArray.size() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CallStackRadixTreeBuilder::build(
|
|
|
|
llvm::MapVector<CallStackId, llvm::SmallVector<FrameId>>
|
|
|
|
&&MemProfCallStackData,
|
[memprof] Improve deserialization performance in V3 (#94787)
We call llvm::sort in a couple of places in the V3 encoding:
- We sort Frames by FrameIds for stability of the output.
- We sort call stacks in the dictionary order to maximize the length
of the common prefix between adjacent call stacks.
It turns out that we can improve the deserialization performance by
modifying the comparison functions -- without changing the format at
all. Both places take advantage of the histogram of Frames -- how
many times each Frame occurs in the call stacks.
- Frames: We serialize popular Frames in the descending order of
popularity for improved cache locality. For two equally popular
Frames, we break a tie by serializing one that tends to appear
earlier in call stacks. Here, "earlier" means a smaller index
within llvm::SmallVector<FrameId>.
- Call Stacks: We sort the call stacks to reduce the number of times
we follow pointers to parents during deserialization. Specifically,
instead of comparing two call stacks in the strcmp style -- integer
comparisons of FrameIds, we compare two FrameIds F1 and F2 with
Histogram[F1] < Histogram[F2] at respective indexes. Since we
encode from the end of the sorted list of call stacks, we tend to
encode popular call stacks first.
Since the two places use the same histogram, we compute it once and
share it in the two places.
Sorting the call stacks reduces the number of "jumps" by 74% when we
deserialize all MemProfRecords. The cycle and instruction counts go
down by 10% and 1.5%, respectively.
If we sort the Frames in addition to the call stacks, then the cycle
and instruction counts go down by 14% and 1.6%, respectively, relative
to the same baseline (that is, without this patch).
2024-06-07 17:25:57 -07:00
|
|
|
const llvm::DenseMap<FrameId, LinearFrameId> &MemProfFrameIndexes,
|
2024-06-07 18:32:58 -07:00
|
|
|
llvm::DenseMap<FrameId, FrameStat> &FrameHistogram) {
|
[memprof] Add CallStackRadixTreeBuilder (#93784)
Call stacks are a huge portion of the MemProf profile, taking up 70+%
of the profile file size.
This patch implements a radix tree to compress call stacks, which are
known to have long common prefixes. Specifically,
CallStackRadixTreeBuilder, introduced in this patch, takes call stacks
in the MemProf profile, sorts them in the dictionary order to maximize
the common prefix between adjacent call stacks, and then encodes a
radix tree into a single array that is ready for serialization.
The resulting radix array is essentially a concatenation of call stack
arrays, each encoded with its length followed by the payload, except
that these arrays contain "instructions" like "skip 7 elements
forward" to borrow common prefixes from other call stacks.
This patch does not integrate with the MemProf
serialization/deserialization infrastructure yet. Once integrated,
the radix tree is expected to roughly halve the file size of the
MemProf profile.
2024-06-06 15:52:45 -07:00
|
|
|
// Take the vector portion of MemProfCallStackData. The vector is exactly
|
|
|
|
// what we need to sort. Also, we no longer need its lookup capability.
|
|
|
|
llvm::SmallVector<CSIdPair, 0> CallStacks = MemProfCallStackData.takeVector();
|
|
|
|
|
|
|
|
// Return early if we have no work to do.
|
|
|
|
if (CallStacks.empty()) {
|
|
|
|
RadixArray.clear();
|
|
|
|
CallStackPos.clear();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
[memprof] Improve deserialization performance in V3 (#94787)
We call llvm::sort in a couple of places in the V3 encoding:
- We sort Frames by FrameIds for stability of the output.
- We sort call stacks in the dictionary order to maximize the length
of the common prefix between adjacent call stacks.
It turns out that we can improve the deserialization performance by
modifying the comparison functions -- without changing the format at
all. Both places take advantage of the histogram of Frames -- how
many times each Frame occurs in the call stacks.
- Frames: We serialize popular Frames in the descending order of
popularity for improved cache locality. For two equally popular
Frames, we break a tie by serializing one that tends to appear
earlier in call stacks. Here, "earlier" means a smaller index
within llvm::SmallVector<FrameId>.
- Call Stacks: We sort the call stacks to reduce the number of times
we follow pointers to parents during deserialization. Specifically,
instead of comparing two call stacks in the strcmp style -- integer
comparisons of FrameIds, we compare two FrameIds F1 and F2 with
Histogram[F1] < Histogram[F2] at respective indexes. Since we
encode from the end of the sorted list of call stacks, we tend to
encode popular call stacks first.
Since the two places use the same histogram, we compute it once and
share it in the two places.
Sorting the call stacks reduces the number of "jumps" by 74% when we
deserialize all MemProfRecords. The cycle and instruction counts go
down by 10% and 1.5%, respectively.
If we sort the Frames in addition to the call stacks, then the cycle
and instruction counts go down by 14% and 1.6%, respectively, relative
to the same baseline (that is, without this patch).
2024-06-07 17:25:57 -07:00
|
|
|
// Sorting the list of call stacks in the dictionary order is sufficient to
|
|
|
|
// maximize the length of the common prefix between two adjacent call stacks
|
|
|
|
// and thus minimize the length of RadixArray. However, we go one step
|
|
|
|
// further and try to reduce the number of times we follow pointers to parents
|
|
|
|
// during deserilization. Consider a poorly encoded radix tree:
|
|
|
|
//
|
|
|
|
// CallStackId 1: f1 -> f2 -> f3
|
|
|
|
// |
|
|
|
|
// CallStackId 2: +--- f4 -> f5
|
|
|
|
// |
|
|
|
|
// CallStackId 3: +--> f6
|
|
|
|
//
|
|
|
|
// Here, f2 and f4 appear once and twice, respectively, in the call stacks.
|
|
|
|
// Once we encode CallStackId 1 into RadixArray, every other call stack with
|
|
|
|
// common prefix f1 ends up pointing to CallStackId 1. Since CallStackId 3
|
|
|
|
// share "f1 f4" with CallStackId 2, CallStackId 3 needs to follow pointers to
|
|
|
|
// parents twice.
|
|
|
|
//
|
|
|
|
// We try to alleviate the situation by sorting the list of call stacks by
|
|
|
|
// comparing the popularity of frames rather than the integer values of
|
|
|
|
// FrameIds. In the example above, f4 is more popular than f2, so we sort the
|
|
|
|
// call stacks and encode them as:
|
|
|
|
//
|
|
|
|
// CallStackId 2: f1 -- f4 -> f5
|
|
|
|
// | |
|
|
|
|
// CallStackId 3: | +--> f6
|
|
|
|
// |
|
|
|
|
// CallStackId 1: +--> f2 -> f3
|
|
|
|
//
|
|
|
|
// Notice that CallStackId 3 follows a pointer to a parent only once.
|
|
|
|
//
|
|
|
|
// All this is a quick-n-dirty trick to reduce the number of jumps. The
|
|
|
|
// proper way would be to compute the weight of each radix tree node -- how
|
|
|
|
// many call stacks use a given radix tree node, and encode a radix tree from
|
|
|
|
// the heaviest node first. We do not do so because that's a lot of work.
|
[memprof] Add CallStackRadixTreeBuilder (#93784)
Call stacks are a huge portion of the MemProf profile, taking up 70+%
of the profile file size.
This patch implements a radix tree to compress call stacks, which are
known to have long common prefixes. Specifically,
CallStackRadixTreeBuilder, introduced in this patch, takes call stacks
in the MemProf profile, sorts them in the dictionary order to maximize
the common prefix between adjacent call stacks, and then encodes a
radix tree into a single array that is ready for serialization.
The resulting radix array is essentially a concatenation of call stack
arrays, each encoded with its length followed by the payload, except
that these arrays contain "instructions" like "skip 7 elements
forward" to borrow common prefixes from other call stacks.
This patch does not integrate with the MemProf
serialization/deserialization infrastructure yet. Once integrated,
the radix tree is expected to roughly halve the file size of the
MemProf profile.
2024-06-06 15:52:45 -07:00
|
|
|
llvm::sort(CallStacks, [&](const CSIdPair &L, const CSIdPair &R) {
|
|
|
|
// Call stacks are stored from leaf to root. Perform comparisons from the
|
|
|
|
// root.
|
|
|
|
return std::lexicographical_compare(
|
|
|
|
L.second.rbegin(), L.second.rend(), R.second.rbegin(), R.second.rend(),
|
[memprof] Improve deserialization performance in V3 (#94787)
We call llvm::sort in a couple of places in the V3 encoding:
- We sort Frames by FrameIds for stability of the output.
- We sort call stacks in the dictionary order to maximize the length
of the common prefix between adjacent call stacks.
It turns out that we can improve the deserialization performance by
modifying the comparison functions -- without changing the format at
all. Both places take advantage of the histogram of Frames -- how
many times each Frame occurs in the call stacks.
- Frames: We serialize popular Frames in the descending order of
popularity for improved cache locality. For two equally popular
Frames, we break a tie by serializing one that tends to appear
earlier in call stacks. Here, "earlier" means a smaller index
within llvm::SmallVector<FrameId>.
- Call Stacks: We sort the call stacks to reduce the number of times
we follow pointers to parents during deserialization. Specifically,
instead of comparing two call stacks in the strcmp style -- integer
comparisons of FrameIds, we compare two FrameIds F1 and F2 with
Histogram[F1] < Histogram[F2] at respective indexes. Since we
encode from the end of the sorted list of call stacks, we tend to
encode popular call stacks first.
Since the two places use the same histogram, we compute it once and
share it in the two places.
Sorting the call stacks reduces the number of "jumps" by 74% when we
deserialize all MemProfRecords. The cycle and instruction counts go
down by 10% and 1.5%, respectively.
If we sort the Frames in addition to the call stacks, then the cycle
and instruction counts go down by 14% and 1.6%, respectively, relative
to the same baseline (that is, without this patch).
2024-06-07 17:25:57 -07:00
|
|
|
[&](FrameId F1, FrameId F2) {
|
|
|
|
uint64_t H1 = FrameHistogram[F1].Count;
|
|
|
|
uint64_t H2 = FrameHistogram[F2].Count;
|
|
|
|
// Popular frames should come later because we encode call stacks from
|
|
|
|
// the last one in the list.
|
|
|
|
if (H1 != H2)
|
|
|
|
return H1 < H2;
|
|
|
|
// For sort stability.
|
|
|
|
return F1 < F2;
|
|
|
|
});
|
[memprof] Add CallStackRadixTreeBuilder (#93784)
Call stacks are a huge portion of the MemProf profile, taking up 70+%
of the profile file size.
This patch implements a radix tree to compress call stacks, which are
known to have long common prefixes. Specifically,
CallStackRadixTreeBuilder, introduced in this patch, takes call stacks
in the MemProf profile, sorts them in the dictionary order to maximize
the common prefix between adjacent call stacks, and then encodes a
radix tree into a single array that is ready for serialization.
The resulting radix array is essentially a concatenation of call stack
arrays, each encoded with its length followed by the payload, except
that these arrays contain "instructions" like "skip 7 elements
forward" to borrow common prefixes from other call stacks.
This patch does not integrate with the MemProf
serialization/deserialization infrastructure yet. Once integrated,
the radix tree is expected to roughly halve the file size of the
MemProf profile.
2024-06-06 15:52:45 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
// Reserve some reasonable amount of storage.
|
|
|
|
RadixArray.clear();
|
|
|
|
RadixArray.reserve(CallStacks.size() * 8);
|
|
|
|
|
|
|
|
// Indexes will grow as long as the longest call stack.
|
|
|
|
Indexes.clear();
|
|
|
|
Indexes.reserve(512);
|
|
|
|
|
|
|
|
// CallStackPos will grow to exactly CallStacks.size() entries.
|
|
|
|
CallStackPos.clear();
|
|
|
|
CallStackPos.reserve(CallStacks.size());
|
|
|
|
|
|
|
|
// Compute the radix array. We encode one call stack at a time, computing the
|
|
|
|
// longest prefix that's shared with the previous call stack we encode. For
|
|
|
|
// each call stack we encode, we remember a mapping from CallStackId to its
|
|
|
|
// position within RadixArray.
|
|
|
|
//
|
|
|
|
// As an optimization, we encode from the last call stack in CallStacks to
|
|
|
|
// reduce the number of times we follow pointers to the parents. Consider the
|
|
|
|
// list of call stacks that has been sorted in the dictionary order:
|
|
|
|
//
|
|
|
|
// Call Stack 1: F1
|
|
|
|
// Call Stack 2: F1 -> F2
|
|
|
|
// Call Stack 3: F1 -> F2 -> F3
|
|
|
|
//
|
|
|
|
// If we traversed CallStacks in the forward order, we would end up with a
|
|
|
|
// radix tree like:
|
|
|
|
//
|
|
|
|
// Call Stack 1: F1
|
|
|
|
// |
|
|
|
|
// Call Stack 2: +---> F2
|
|
|
|
// |
|
|
|
|
// Call Stack 3: +---> F3
|
|
|
|
//
|
|
|
|
// Notice that each call stack jumps to the previous one. However, if we
|
|
|
|
// traverse CallStacks in the reverse order, then Call Stack 3 has the
|
|
|
|
// complete call stack encoded without any pointers. Call Stack 1 and 2 point
|
|
|
|
// to appropriate prefixes of Call Stack 3.
|
|
|
|
const llvm::SmallVector<FrameId> *Prev = nullptr;
|
|
|
|
for (const auto &[CSId, CallStack] : llvm::reverse(CallStacks)) {
|
|
|
|
LinearCallStackId Pos =
|
|
|
|
encodeCallStack(&CallStack, Prev, MemProfFrameIndexes);
|
|
|
|
CallStackPos.insert({CSId, Pos});
|
|
|
|
Prev = &CallStack;
|
|
|
|
}
|
|
|
|
|
|
|
|
// "RadixArray.size() - 1" below is problematic if RadixArray is empty.
|
|
|
|
assert(!RadixArray.empty());
|
|
|
|
|
|
|
|
// Reverse the radix array in place. We do so mostly for intuitive
|
|
|
|
// deserialization where we would read the length field and then the call
|
|
|
|
// stack frames proper just like any other array deserialization, except
|
|
|
|
// that we have occasional jumps to take advantage of prefixes.
|
|
|
|
for (size_t I = 0, J = RadixArray.size() - 1; I < J; ++I, --J)
|
|
|
|
std::swap(RadixArray[I], RadixArray[J]);
|
|
|
|
|
|
|
|
// "Reverse" the indexes stored in CallStackPos.
|
|
|
|
for (auto &[K, V] : CallStackPos)
|
|
|
|
V = RadixArray.size() - 1 - V;
|
|
|
|
}
|
|
|
|
|
[memprof] Improve deserialization performance in V3 (#94787)
We call llvm::sort in a couple of places in the V3 encoding:
- We sort Frames by FrameIds for stability of the output.
- We sort call stacks in the dictionary order to maximize the length
of the common prefix between adjacent call stacks.
It turns out that we can improve the deserialization performance by
modifying the comparison functions -- without changing the format at
all. Both places take advantage of the histogram of Frames -- how
many times each Frame occurs in the call stacks.
- Frames: We serialize popular Frames in the descending order of
popularity for improved cache locality. For two equally popular
Frames, we break a tie by serializing one that tends to appear
earlier in call stacks. Here, "earlier" means a smaller index
within llvm::SmallVector<FrameId>.
- Call Stacks: We sort the call stacks to reduce the number of times
we follow pointers to parents during deserialization. Specifically,
instead of comparing two call stacks in the strcmp style -- integer
comparisons of FrameIds, we compare two FrameIds F1 and F2 with
Histogram[F1] < Histogram[F2] at respective indexes. Since we
encode from the end of the sorted list of call stacks, we tend to
encode popular call stacks first.
Since the two places use the same histogram, we compute it once and
share it in the two places.
Sorting the call stacks reduces the number of "jumps" by 74% when we
deserialize all MemProfRecords. The cycle and instruction counts go
down by 10% and 1.5%, respectively.
If we sort the Frames in addition to the call stacks, then the cycle
and instruction counts go down by 14% and 1.6%, respectively, relative
to the same baseline (that is, without this patch).
2024-06-07 17:25:57 -07:00
|
|
|
llvm::DenseMap<FrameId, FrameStat>
|
|
|
|
computeFrameHistogram(llvm::MapVector<CallStackId, llvm::SmallVector<FrameId>>
|
|
|
|
&MemProfCallStackData) {
|
|
|
|
llvm::DenseMap<FrameId, FrameStat> Histogram;
|
|
|
|
|
|
|
|
for (const auto &KV : MemProfCallStackData) {
|
|
|
|
const auto &CS = KV.second;
|
|
|
|
for (unsigned I = 0, E = CS.size(); I != E; ++I) {
|
|
|
|
auto &S = Histogram[CS[I]];
|
|
|
|
++S.Count;
|
|
|
|
S.PositionSum += I;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Histogram;
|
|
|
|
}
|
|
|
|
|
2024-03-25 14:21:49 -07:00
|
|
|
void verifyIndexedMemProfRecord(const IndexedMemProfRecord &Record) {
|
|
|
|
for (const auto &AS : Record.AllocSites) {
|
|
|
|
assert(AS.CSId == hashCallStack(AS.CallStack));
|
|
|
|
(void)AS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-23 19:50:15 -07:00
|
|
|
void verifyFunctionProfileData(
|
|
|
|
const llvm::MapVector<GlobalValue::GUID, IndexedMemProfRecord>
|
|
|
|
&FunctionProfileData) {
|
|
|
|
for (const auto &[GUID, Record] : FunctionProfileData) {
|
|
|
|
(void)GUID;
|
2024-03-25 14:21:49 -07:00
|
|
|
verifyIndexedMemProfRecord(Record);
|
2024-03-23 19:50:15 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-17 16:01:31 -08:00
|
|
|
} // namespace memprof
|
|
|
|
} // namespace llvm
|