Skip to content

Commit a2377b6

Browse files
committed
[CGData] Lazy loading support for stable function map
The stable function map could be huge for a large application. Fully loading it is slow and consumes a significant amount of memory, which is unnecessary and drastically slows down compilation especially for non-LTO and distributed-ThinLTO setups. This patch introduces an opt-in lazy loading support for the stable function map. The detailed changes are: - `StableFunctionMap` - The map now stores entries in an `EntryStorage` struct, which includes offsets for serialized entries and a `std::once_flag` for thread-safe lazy loading. - The underlying map type is changed from `DenseMap` to `std::unordered_map` for compatibility with `std::once_flag`. - `contains()`, `size()` and `at()` are implemented to only load requested entries on demand. - Lazy Loading Mechanism - When reading indexed codegen data, if the newly-introduced `-indexed-codegen-data-lazy-loading` flag is set, the stable function map is not fully deserialized up front. The binary format for the stable function map now includes offsets and sizes to support lazy loading. - The safety of lazy loading is guarded by the once flag per function hash. This guarantees that even in a multi-threaded environment, the deserialization for a given function hash will happen exactly once. The first thread to request it performs the load, and subsequent threads will wait for it to complete before using the data. For single-threaded builds, the overhead is negligible (a single check on the once flag). For multi-threaded scenarios, users can omit the flag to retain the previous eager-loading behavior.
1 parent 283c47b commit a2377b6

20 files changed

+298
-84
lines changed

llvm/include/llvm/CGData/CodeGenData.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ enum CGDataVersion {
285285
// Version 3 adds the total size of the Names in the stable function map so
286286
// we can skip reading them into the memory for non-assertion builds.
287287
Version3 = 3,
288+
// Version 4 adjusts the structure of stable function merging map for
289+
// efficient lazy loading support.
290+
Version4 = 4,
288291
CurrentVersion = CG_DATA_INDEX_VERSION
289292
};
290293
const uint64_t Version = CGDataVersion::CurrentVersion;

llvm/include/llvm/CGData/CodeGenData.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ CG_DATA_SECT_ENTRY(CG_merge, CG_DATA_QUOTE(CG_DATA_MERGE_COMMON),
4949
#endif
5050

5151
/* Indexed codegen data format version (start from 1). */
52-
#define CG_DATA_INDEX_VERSION 3
52+
#define CG_DATA_INDEX_VERSION 4

llvm/include/llvm/CGData/StableFunctionMap.h

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include "llvm/ADT/StringMap.h"
2121
#include "llvm/IR/StructuralHash.h"
2222
#include "llvm/Support/Compiler.h"
23+
#include "llvm/Support/MemoryBuffer.h"
24+
#include <mutex>
2325

2426
namespace llvm {
2527

@@ -72,11 +74,29 @@ struct StableFunctionMap {
7274
IndexOperandHashMap(std::move(IndexOperandHashMap)) {}
7375
};
7476

75-
using HashFuncsMapType =
76-
DenseMap<stable_hash, SmallVector<std::unique_ptr<StableFunctionEntry>>>;
77+
using StableFunctionEntries =
78+
SmallVector<std::unique_ptr<StableFunctionEntry>>;
79+
80+
/// In addition to the deserialized StableFunctionEntry, the struct stores
81+
/// the offsets of corresponding serialized stable function entries, and a
82+
/// once flag for safe lazy loading in a multithreaded environment.
83+
struct EntryStorage {
84+
StableFunctionEntries Entries;
85+
86+
private:
87+
SmallVector<uint64_t> Offsets;
88+
std::once_flag LazyLoadFlag;
89+
friend struct StableFunctionMap;
90+
friend struct StableFunctionMapRecord;
91+
};
92+
93+
// Note: DenseMap requires value type to be copyable even if only using
94+
// in-place insertion. Use STL instead. This also affects the
95+
// deletion-while-iteration in finalize().
96+
using HashFuncsMapType = std::unordered_map<stable_hash, EntryStorage>;
7797

7898
/// Get the HashToFuncs map for serialization.
79-
const HashFuncsMapType &getFunctionMap() const { return HashToFuncs; }
99+
const HashFuncsMapType &getFunctionMap() const;
80100

81101
/// Get the NameToId vector for serialization.
82102
ArrayRef<std::string> getNames() const { return IdToName; }
@@ -99,6 +119,13 @@ struct StableFunctionMap {
99119
/// \returns true if there is no stable function entry.
100120
bool empty() const { return size() == 0; }
101121

122+
bool contains(HashFuncsMapType::key_type FunctionHash) const {
123+
return HashToFuncs.count(FunctionHash) > 0;
124+
}
125+
126+
const StableFunctionEntries &
127+
at(HashFuncsMapType::key_type FunctionHash) const;
128+
102129
enum SizeType {
103130
UniqueHashCount, // The number of unique hashes in HashToFuncs.
104131
TotalFunctionCount, // The number of total functions in HashToFuncs.
@@ -119,17 +146,31 @@ struct StableFunctionMap {
119146
/// `StableFunctionEntry` is ready for insertion.
120147
void insert(std::unique_ptr<StableFunctionEntry> FuncEntry) {
121148
assert(!Finalized && "Cannot insert after finalization");
122-
HashToFuncs[FuncEntry->Hash].emplace_back(std::move(FuncEntry));
149+
HashToFuncs[FuncEntry->Hash].Entries.emplace_back(std::move(FuncEntry));
123150
}
124151

152+
void deserializeLazyLoadingEntry(HashFuncsMapType::iterator It);
153+
154+
/// Eagerly deserialize all the unloaded entries in the lazy loading map.
155+
void deserializeLazyLoadingEntries();
156+
157+
bool isLazilyLoaded() const { return (bool)Buffer; }
158+
125159
/// A map from a stable_hash to a vector of functions with that hash.
126-
HashFuncsMapType HashToFuncs;
160+
mutable HashFuncsMapType HashToFuncs;
127161
/// A vector of strings to hold names.
128162
SmallVector<std::string> IdToName;
129163
/// A map from StringRef (name) to an ID.
130164
StringMap<unsigned> NameToId;
131165
/// True if the function map is finalized with minimal content.
132166
bool Finalized = false;
167+
/// The memory buffer that contains the serialized stable function map for
168+
/// lazy loading.
169+
/// Non-empty only if this StableFunctionMap is created from a MemoryBuffer
170+
/// (i.e. by IndexedCodeGenDataReader::read()) and lazily deserialized.
171+
std::shared_ptr<MemoryBuffer> Buffer;
172+
/// Whether to read stable function names from the buffer.
173+
bool ReadStableFunctionMapNames = true;
133174

134175
friend struct StableFunctionMapRecord;
135176
};

llvm/include/llvm/CGData/StableFunctionMapRecord.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ struct StableFunctionMapRecord {
4040
const StableFunctionMap *FunctionMap,
4141
std::vector<CGDataPatchItem> &PatchItems);
4242

43+
/// A static helper function to deserialize the stable function map entry.
44+
/// Ptr should be pointing to the start of the fixed-sized fields of the
45+
/// entry when passed in.
46+
LLVM_ABI static void deserializeEntry(const unsigned char *Ptr,
47+
stable_hash Hash,
48+
StableFunctionMap *FunctionMap,
49+
bool ReadStableFunctionMapNames = true);
50+
4351
/// Serialize the stable function map to a raw_ostream.
4452
LLVM_ABI void serialize(raw_ostream &OS,
4553
std::vector<CGDataPatchItem> &PatchItems) const;
@@ -48,6 +56,13 @@ struct StableFunctionMapRecord {
4856
LLVM_ABI void deserialize(const unsigned char *&Ptr,
4957
bool ReadStableFunctionMapNames = true);
5058

59+
/// Lazily deserialize the stable function map from `Buffer` starting at
60+
/// `Offset`. The individial stable function entry would be read lazily from
61+
/// `Buffer` when the function map is accessed.
62+
LLVM_ABI void lazyDeserialize(std::shared_ptr<MemoryBuffer> Buffer,
63+
uint64_t Offset,
64+
bool ReadStableFunctionMapNames = true);
65+
5166
/// Serialize the stable function map to a YAML stream.
5267
LLVM_ABI void serializeYAML(yaml::Output &YOS) const;
5368

@@ -70,6 +85,10 @@ struct StableFunctionMapRecord {
7085
yaml::Output YOS(OS);
7186
serializeYAML(YOS);
7287
}
88+
89+
private:
90+
void deserialize(const unsigned char *&Ptr, bool ReadStableFunctionMapNames,
91+
bool Lazy);
7392
};
7493

7594
} // namespace llvm

llvm/lib/CGData/CodeGenData.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ Expected<Header> Header::readFromBuffer(const unsigned char *Curr) {
186186
return make_error<CGDataError>(cgdata_error::unsupported_version);
187187
H.DataKind = endian::readNext<uint32_t, endianness::little, unaligned>(Curr);
188188

189-
static_assert(IndexedCGData::CGDataVersion::CurrentVersion == Version3,
189+
static_assert(IndexedCGData::CGDataVersion::CurrentVersion == Version4,
190190
"Please update the offset computation below if a new field has "
191191
"been added to the header.");
192192
H.OutlinedHashTreeOffset =

llvm/lib/CGData/CodeGenDataReader.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ static cl::opt<bool> IndexedCodeGenDataReadFunctionMapNames(
2626
"disabled to save memory and time for final consumption of the "
2727
"indexed CodeGenData in production."));
2828

29+
cl::opt<bool> IndexedCodeGenDataLazyLoading(
30+
"indexed-codegen-data-lazy-loading", cl::init(false), cl::Hidden,
31+
cl::desc(
32+
"Lazily load indexed CodeGenData. Enable to save memory and time "
33+
"for final consumption of the indexed CodeGenData in production."));
34+
2935
namespace llvm {
3036

3137
static Expected<std::unique_ptr<MemoryBuffer>>
@@ -109,11 +115,20 @@ Error IndexedCodeGenDataReader::read() {
109115
return error(cgdata_error::eof);
110116
HashTreeRecord.deserialize(Ptr);
111117
}
118+
119+
// TODO: lazy loading support for outlined hash tree.
120+
std::shared_ptr<MemoryBuffer> SharedDataBuffer = std::move(DataBuffer);
112121
if (hasStableFunctionMap()) {
113122
const unsigned char *Ptr = Start + Header.StableFunctionMapOffset;
114123
if (Ptr >= End)
115124
return error(cgdata_error::eof);
116-
FunctionMapRecord.deserialize(Ptr, IndexedCodeGenDataReadFunctionMapNames);
125+
if (IndexedCodeGenDataLazyLoading)
126+
FunctionMapRecord.lazyDeserialize(SharedDataBuffer,
127+
Header.StableFunctionMapOffset,
128+
IndexedCodeGenDataReadFunctionMapNames);
129+
else
130+
FunctionMapRecord.deserialize(Ptr,
131+
IndexedCodeGenDataReadFunctionMapNames);
117132
}
118133

119134
return success();

llvm/lib/CGData/StableFunctionMap.cpp

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515

1616
#include "llvm/CGData/StableFunctionMap.h"
1717
#include "llvm/ADT/SmallSet.h"
18+
#include "llvm/CGData/StableFunctionMapRecord.h"
1819
#include "llvm/Support/CommandLine.h"
1920
#include "llvm/Support/Debug.h"
21+
#include <mutex>
2022

2123
#define DEBUG_TYPE "stable-function-map"
2224

@@ -93,9 +95,10 @@ void StableFunctionMap::insert(const StableFunction &Func) {
9395

9496
void StableFunctionMap::merge(const StableFunctionMap &OtherMap) {
9597
assert(!Finalized && "Cannot merge after finalization");
98+
deserializeLazyLoadingEntries();
9699
for (auto &[Hash, Funcs] : OtherMap.HashToFuncs) {
97-
auto &ThisFuncs = HashToFuncs[Hash];
98-
for (auto &Func : Funcs) {
100+
auto &ThisFuncs = HashToFuncs[Hash].Entries;
101+
for (auto &Func : Funcs.Entries) {
99102
auto FuncNameId =
100103
getIdOrCreateForName(*OtherMap.getNameForId(Func->FunctionNameId));
101104
auto ModuleNameId =
@@ -114,25 +117,61 @@ size_t StableFunctionMap::size(SizeType Type) const {
114117
case UniqueHashCount:
115118
return HashToFuncs.size();
116119
case TotalFunctionCount: {
120+
const_cast<StableFunctionMap *>(this)->deserializeLazyLoadingEntries();
117121
size_t Count = 0;
118122
for (auto &Funcs : HashToFuncs)
119-
Count += Funcs.second.size();
123+
Count += Funcs.second.Entries.size();
120124
return Count;
121125
}
122126
case MergeableFunctionCount: {
127+
const_cast<StableFunctionMap *>(this)->deserializeLazyLoadingEntries();
123128
size_t Count = 0;
124129
for (auto &[Hash, Funcs] : HashToFuncs)
125-
if (Funcs.size() >= 2)
126-
Count += Funcs.size();
130+
if (Funcs.Entries.size() >= 2)
131+
Count += Funcs.Entries.size();
127132
return Count;
128133
}
129134
}
130135
llvm_unreachable("Unhandled size type");
131136
}
132137

138+
const StableFunctionMap::StableFunctionEntries &
139+
StableFunctionMap::at(HashFuncsMapType::key_type FunctionHash) const {
140+
auto It = HashToFuncs.find(FunctionHash);
141+
if (isLazilyLoaded())
142+
const_cast<StableFunctionMap *>(this)->deserializeLazyLoadingEntry(It);
143+
return It->second.Entries;
144+
}
145+
146+
void StableFunctionMap::deserializeLazyLoadingEntry(
147+
HashFuncsMapType::iterator It) {
148+
assert(isLazilyLoaded() && "Cannot deserialize non-lazily-loaded map");
149+
std::call_once(It->second.LazyLoadFlag, [this, It]() {
150+
for (auto Offset : It->second.Offsets)
151+
StableFunctionMapRecord::deserializeEntry(
152+
reinterpret_cast<const unsigned char *>(Offset), It->first, this,
153+
ReadStableFunctionMapNames);
154+
});
155+
}
156+
157+
void ::StableFunctionMap::deserializeLazyLoadingEntries() {
158+
if (!isLazilyLoaded())
159+
return;
160+
for (auto It = HashToFuncs.begin(); It != HashToFuncs.end(); ++It)
161+
deserializeLazyLoadingEntry(It);
162+
}
163+
164+
const StableFunctionMap::HashFuncsMapType &
165+
StableFunctionMap::getFunctionMap() const {
166+
// Ensure all entries are deserialized before returning the raw map.
167+
if (isLazilyLoaded())
168+
const_cast<StableFunctionMap *>(this)->deserializeLazyLoadingEntries();
169+
return HashToFuncs;
170+
}
171+
133172
using ParamLocs = SmallVector<IndexPair>;
134-
static void removeIdenticalIndexPair(
135-
SmallVector<std::unique_ptr<StableFunctionMap::StableFunctionEntry>> &SFS) {
173+
static void
174+
removeIdenticalIndexPair(StableFunctionMap::StableFunctionEntries &SFS) {
136175
auto &RSF = SFS[0];
137176
unsigned StableFunctionCount = SFS.size();
138177

@@ -159,9 +198,7 @@ static void removeIdenticalIndexPair(
159198
SF->IndexOperandHashMap->erase(Pair);
160199
}
161200

162-
static bool isProfitable(
163-
const SmallVector<std::unique_ptr<StableFunctionMap::StableFunctionEntry>>
164-
&SFS) {
201+
static bool isProfitable(const StableFunctionMap::StableFunctionEntries &SFS) {
165202
unsigned StableFunctionCount = SFS.size();
166203
if (StableFunctionCount < GlobalMergingMinMerges)
167204
return false;
@@ -202,8 +239,11 @@ static bool isProfitable(
202239
}
203240

204241
void StableFunctionMap::finalize(bool SkipTrim) {
242+
deserializeLazyLoadingEntries();
243+
SmallVector<HashFuncsMapType::iterator> ToDelete;
205244
for (auto It = HashToFuncs.begin(); It != HashToFuncs.end(); ++It) {
206-
auto &[StableHash, SFS] = *It;
245+
auto &[StableHash, Storage] = *It;
246+
auto &SFS = Storage.Entries;
207247

208248
// Group stable functions by ModuleIdentifier.
209249
llvm::stable_sort(SFS, [&](const std::unique_ptr<StableFunctionEntry> &L,
@@ -236,7 +276,7 @@ void StableFunctionMap::finalize(bool SkipTrim) {
236276
}
237277
}
238278
if (Invalid) {
239-
HashToFuncs.erase(It);
279+
ToDelete.push_back(It);
240280
continue;
241281
}
242282

@@ -248,8 +288,10 @@ void StableFunctionMap::finalize(bool SkipTrim) {
248288
removeIdenticalIndexPair(SFS);
249289

250290
if (!isProfitable(SFS))
251-
HashToFuncs.erase(It);
291+
ToDelete.push_back(It);
252292
}
293+
for (auto It : ToDelete)
294+
HashToFuncs.erase(It);
253295

254296
Finalized = true;
255297
}

0 commit comments

Comments
 (0)