Skip to content

Commit 9f991ed

Browse files
committed
[mlir][StorageUniquer] Use allocators per thread instead of per shard
This greatly reduces the number of allocators we create, while still retaining thread safety. Reducing the number of allocators is much better for locality and memory usage; this revision drops memory usage for some MLIR heavy workloads (with lots of attributes/types) by >=5%. This is due to the observation that the number of threads is effectively always smaller than the number of parametric attributes/types. Differential Revision: https://reviews.llvm.org/D145991
1 parent 96118f1 commit 9f991ed

File tree

1 file changed

+63
-36
lines changed

1 file changed

+63
-36
lines changed

mlir/lib/Support/StorageUniquer.cpp

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,6 @@ class ParametricStorageUniquer {
8282
/// The set containing the allocated storage instances.
8383
StorageTypeSet instances;
8484

85-
/// Allocator to use when constructing derived instances.
86-
StorageAllocator allocator;
87-
8885
#if LLVM_ENABLE_THREADS != 0
8986
/// A mutex to keep uniquing thread-safe.
9087
llvm::sys::SmartRWMutex<true> mutex;
@@ -93,13 +90,12 @@ class ParametricStorageUniquer {
9390

9491
/// Get or create an instance of a param derived type in an thread-unsafe
9592
/// fashion.
96-
BaseStorage *
97-
getOrCreateUnsafe(Shard &shard, LookupKey &key,
98-
function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
93+
BaseStorage *getOrCreateUnsafe(Shard &shard, LookupKey &key,
94+
function_ref<BaseStorage *()> ctorFn) {
9995
auto existing = shard.instances.insert_as({key.hashValue}, key);
10096
BaseStorage *&storage = existing.first->storage;
10197
if (existing.second)
102-
storage = ctorFn(shard.allocator);
98+
storage = ctorFn();
10399
return storage;
104100
}
105101

@@ -135,10 +131,9 @@ class ParametricStorageUniquer {
135131
}
136132
}
137133
/// Get or create an instance of a parametric type.
138-
BaseStorage *
139-
getOrCreate(bool threadingIsEnabled, unsigned hashValue,
140-
function_ref<bool(const BaseStorage *)> isEqual,
141-
function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
134+
BaseStorage *getOrCreate(bool threadingIsEnabled, unsigned hashValue,
135+
function_ref<bool(const BaseStorage *)> isEqual,
136+
function_ref<BaseStorage *()> ctorFn) {
142137
Shard &shard = getShard(hashValue);
143138
ParametricStorageUniquer::LookupKey lookupKey{hashValue, isEqual};
144139
if (!threadingIsEnabled)
@@ -163,17 +158,20 @@ class ParametricStorageUniquer {
163158
llvm::sys::SmartScopedWriter<true> typeLock(shard.mutex);
164159
return localInst = getOrCreateUnsafe(shard, lookupKey, ctorFn);
165160
}
161+
166162
/// Run a mutation function on the provided storage object in a thread-safe
167163
/// way.
168-
LogicalResult
169-
mutate(bool threadingIsEnabled, BaseStorage *storage,
170-
function_ref<LogicalResult(StorageAllocator &)> mutationFn) {
171-
Shard &shard = getShardFor(storage);
164+
LogicalResult mutate(bool threadingIsEnabled, BaseStorage *storage,
165+
function_ref<LogicalResult()> mutationFn) {
172166
if (!threadingIsEnabled)
173-
return mutationFn(shard.allocator);
167+
return mutationFn();
174168

169+
// Get a shard to use for mutating this storage instance. It doesn't need to
170+
// be the same shard as the original allocation, but does need to be
171+
// deterministic.
172+
Shard &shard = getShard(llvm::hash_value(storage));
175173
llvm::sys::SmartScopedWriter<true> lock(shard.mutex);
176-
return mutationFn(shard.allocator);
174+
return mutationFn();
177175
}
178176

179177
private:
@@ -197,18 +195,6 @@ class ParametricStorageUniquer {
197195
return *shard;
198196
}
199197

200-
/// Return the shard that allocated the provided storage object.
201-
Shard &getShardFor(BaseStorage *storage) {
202-
for (size_t i = 0; i != numShards; ++i) {
203-
if (Shard *shard = shards[i].load(std::memory_order_acquire)) {
204-
llvm::sys::SmartScopedReader<true> lock(shard->mutex);
205-
if (shard->allocator.allocated(storage))
206-
return *shard;
207-
}
208-
}
209-
llvm_unreachable("expected storage object to have a valid shard");
210-
}
211-
212198
/// A thread local cache for storage objects. This helps to reduce the lock
213199
/// contention when an object already existing in the cache.
214200
ThreadLocalCache<StorageTypeSet> localCache;
@@ -281,8 +267,9 @@ struct StorageUniquerImpl {
281267
assert(parametricUniquers.count(id) &&
282268
"creating unregistered storage instance");
283269
ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
284-
return storageUniquer.getOrCreate(threadingIsEnabled, hashValue, isEqual,
285-
ctorFn);
270+
return storageUniquer.getOrCreate(
271+
threadingIsEnabled, hashValue, isEqual,
272+
[&] { return ctorFn(getThreadSafeAllocator()); });
286273
}
287274

288275
/// Run a mutation function on the provided storage object in a thread-safe
@@ -293,7 +280,34 @@ struct StorageUniquerImpl {
293280
assert(parametricUniquers.count(id) &&
294281
"mutating unregistered storage instance");
295282
ParametricStorageUniquer &storageUniquer = *parametricUniquers[id];
296-
return storageUniquer.mutate(threadingIsEnabled, storage, mutationFn);
283+
return storageUniquer.mutate(threadingIsEnabled, storage, [&] {
284+
return mutationFn(getThreadSafeAllocator());
285+
});
286+
}
287+
288+
/// Return an allocator that can be used to safely allocate instances on the
289+
/// current thread.
290+
StorageAllocator &getThreadSafeAllocator() {
291+
#if LLVM_ENABLE_THREADS != 0
292+
if (!threadingIsEnabled)
293+
return allocator;
294+
295+
// If the allocator has not been initialized, create a new one.
296+
StorageAllocator *&threadAllocator = threadSafeAllocator.get();
297+
if (!threadAllocator) {
298+
threadAllocator = new StorageAllocator();
299+
300+
// Record this allocator, given that we don't want it to be destroyed when
301+
// the thread dies.
302+
llvm::sys::SmartScopedLock<true> lock(threadAllocatorMutex);
303+
threadAllocators.push_back(
304+
std::unique_ptr<StorageAllocator>(threadAllocator));
305+
}
306+
307+
return *threadAllocator;
308+
#else
309+
return allocator;
310+
#endif
297311
}
298312

299313
//===--------------------------------------------------------------------===//
@@ -314,6 +328,22 @@ struct StorageUniquerImpl {
314328
// Instance Storage
315329
//===--------------------------------------------------------------------===//
316330

331+
#if LLVM_ENABLE_THREADS != 0
332+
/// A thread local set of allocators used for uniquing parametric instances,
333+
/// or other data allocated in thread volatile situations.
334+
ThreadLocalCache<StorageAllocator *> threadSafeAllocator;
335+
336+
/// All of the allocators that have been created for thread based allocation.
337+
std::vector<std::unique_ptr<StorageAllocator>> threadAllocators;
338+
339+
/// A mutex used for safely adding a new thread allocator.
340+
llvm::sys::SmartMutex<true> threadAllocatorMutex;
341+
#endif
342+
343+
/// Main allocator used for uniquing singleton instances, and other state when
344+
/// thread safety is guaranteed.
345+
StorageAllocator allocator;
346+
317347
/// Map of type ids to the storage uniquer to use for registered objects.
318348
DenseMap<TypeID, std::unique_ptr<ParametricStorageUniquer>>
319349
parametricUniquers;
@@ -322,9 +352,6 @@ struct StorageUniquerImpl {
322352
/// singleton.
323353
DenseMap<TypeID, BaseStorage *> singletonInstances;
324354

325-
/// Allocator used for uniquing singleton instances.
326-
StorageAllocator singletonAllocator;
327-
328355
/// Flag specifying if multi-threading is enabled within the uniquer.
329356
bool threadingIsEnabled = true;
330357
};
@@ -378,7 +405,7 @@ void StorageUniquer::registerSingletonImpl(
378405
TypeID id, function_ref<BaseStorage *(StorageAllocator &)> ctorFn) {
379406
assert(!impl->singletonInstances.count(id) &&
380407
"storage class already registered");
381-
impl->singletonInstances.try_emplace(id, ctorFn(impl->singletonAllocator));
408+
impl->singletonInstances.try_emplace(id, ctorFn(impl->allocator));
382409
}
383410

384411
/// Implementation for mutating an instance of a derived storage.

0 commit comments

Comments
 (0)