diff --git a/llvm/include/llvm/DWARFLinkerParallel/StringPool.h b/llvm/include/llvm/DWARFLinkerParallel/StringPool.h --- a/llvm/include/llvm/DWARFLinkerParallel/StringPool.h +++ b/llvm/include/llvm/DWARFLinkerParallel/StringPool.h @@ -12,6 +12,7 @@ #include "llvm/ADT/ConcurrentHashtable.h" #include "llvm/CodeGen/DwarfStringPoolEntry.h" #include "llvm/Support/Allocator.h" +#include "llvm/Support/PerThreadBumpPtrAllocator.h" #include #include @@ -22,28 +23,6 @@ /// and a string body which is placed right after StringEntry. using StringEntry = StringMapEntry; -class StringAllocator : public AllocatorBase { -public: - inline LLVM_ATTRIBUTE_RETURNS_NONNULL void *Allocate(size_t Size, - size_t Alignment) { -#if LLVM_ENABLE_THREADS - std::lock_guard Guard(AllocatorMutex); -#endif - - return Allocator.Allocate(Size, Align(Alignment)); - } - - // Pull in base class overloads. - using AllocatorBase::Allocate; - -private: -#if LLVM_ENABLE_THREADS - std::mutex AllocatorMutex; -#endif - - BumpPtrAllocator Allocator; -}; - class StringPoolEntryInfo { public: /// \returns Hash value for the specified \p Key. @@ -62,28 +41,31 @@ } /// \returns newly created object of KeyDataTy type. - static inline StringEntry *create(const StringRef &Key, - StringAllocator &Allocator) { + static inline StringEntry * + create(const StringRef &Key, parallel::PerThreadBumpPtrAllocator &Allocator) { return StringEntry::create(Key, Allocator); } }; class StringPool - : public ConcurrentHashTableByPtr { public: StringPool() - : ConcurrentHashTableByPtr(Allocator) {} StringPool(size_t InitialSize) - : ConcurrentHashTableByPtr(Allocator, InitialSize) {} - StringAllocator &getAllocatorRef() { return Allocator; } + parallel::PerThreadBumpPtrAllocator &getAllocatorRef() { return Allocator; } private: - StringAllocator Allocator; + parallel::PerThreadBumpPtrAllocator Allocator; }; } // end of namespace dwarflinker_parallel diff --git a/llvm/include/llvm/Support/Parallel.h b/llvm/include/llvm/Support/Parallel.h --- a/llvm/include/llvm/Support/Parallel.h +++ b/llvm/include/llvm/Support/Parallel.h @@ -48,8 +48,11 @@ inline unsigned getThreadIndex() { GET_THREAD_INDEX_IMPL; } #endif + +size_t getThreadCount(); #else inline unsigned getThreadIndex() { return 0; } +inline size_t getThreadCount() { return 1; } #endif namespace detail { diff --git a/llvm/include/llvm/Support/PerThreadBumpPtrAllocator.h b/llvm/include/llvm/Support/PerThreadBumpPtrAllocator.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Support/PerThreadBumpPtrAllocator.h @@ -0,0 +1,120 @@ +//===- PerThreadBumpPtrAllocator.h ------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_PERTHREADBUMPPTRALLOCATOR_H +#define LLVM_SUPPORT_PERTHREADBUMPPTRALLOCATOR_H + +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Parallel.h" + +namespace llvm { +namespace parallel { + +/// PerThreadAllocator is used in conjunction with ThreadPoolExecutor to allow +/// per-thread allocations. It wraps a possibly thread-unsafe allocator, +/// e.g. BumpPtrAllocator. PerThreadAllocator must be used with only main thread +/// or threads created by ThreadPoolExecutor, as it utilizes getThreadIndex, +/// which is set by ThreadPoolExecutor. To work properly, ThreadPoolExecutor +/// should be initialized before PerThreadAllocator is created. +/// TODO: The same approach might be implemented for ThreadPool. + +template +class PerThreadAllocator + : public AllocatorBase> { +public: + PerThreadAllocator() + : NumOfAllocators(parallel::getThreadCount()), + Allocators(std::make_unique(NumOfAllocators)) {} + + /// \defgroup Methods which could be called asynchronously: + /// + /// @{ + + using AllocatorBase>::Allocate; + + using AllocatorBase>::Deallocate; + + /// Allocate \a Size bytes of \a Alignment aligned memory. + void *Allocate(size_t Size, size_t Alignment) { + assert(getThreadIndex() < NumOfAllocators); + return Allocators[getThreadIndex()].Allocate(Size, Alignment); + } + + /// Deallocate \a Ptr to \a Size bytes of memory allocated by this + /// allocator. + void Deallocate(const void *Ptr, size_t Size, size_t Alignment) { + assert(getThreadIndex() < NumOfAllocators); + return Allocators[getThreadIndex()].Deallocate(Ptr, Size, Alignment); + } + + /// Return allocator corresponding to the current thread. + AllocatorTy &getThreadLocalAllocator() { + assert(getThreadIndex() < NumOfAllocators); + return Allocators[getThreadIndex()]; + } + + // Return number of used allocators. + size_t getNumberOfAllocators() const { return NumOfAllocators; } + /// @} + + /// \defgroup Methods which could not be called asynchronously: + /// + /// @{ + + /// Reset state of allocators. + void Reset() { + for (size_t Idx = 0; Idx < getNumberOfAllocators(); Idx++) + Allocators[Idx].Reset(); + } + + /// Return total memory size used by all allocators. + size_t getTotalMemory() const { + size_t TotalMemory = 0; + + for (size_t Idx = 0; Idx < getNumberOfAllocators(); Idx++) + TotalMemory += Allocators[Idx].getTotalMemory(); + + return TotalMemory; + } + + /// Return allocated size by all allocators. + size_t getBytesAllocated() const { + size_t BytesAllocated = 0; + + for (size_t Idx = 0; Idx < getNumberOfAllocators(); Idx++) + BytesAllocated += Allocators[Idx].getBytesAllocated(); + + return BytesAllocated; + } + + /// Set red zone for all allocators. + void setRedZoneSize(size_t NewSize) { + for (size_t Idx = 0; Idx < getNumberOfAllocators(); Idx++) + Allocators[Idx].setRedZoneSize(NewSize); + } + + /// Print statistic for each allocator. + void PrintStats() const { + for (size_t Idx = 0; Idx < getNumberOfAllocators(); Idx++) { + errs() << "\n Allocator " << Idx << "\n"; + Allocators[Idx].PrintStats(); + } + } + /// @} + +protected: + size_t NumOfAllocators; + std::unique_ptr Allocators; +}; + +using PerThreadBumpPtrAllocator = PerThreadAllocator; + +} // end namespace parallel +} // end namespace llvm + +#endif // LLVM_SUPPORT_PERTHREADBUMPPTRALLOCATOR_H diff --git a/llvm/lib/Support/Parallel.cpp b/llvm/lib/Support/Parallel.cpp --- a/llvm/lib/Support/Parallel.cpp +++ b/llvm/lib/Support/Parallel.cpp @@ -40,6 +40,7 @@ public: virtual ~Executor() = default; virtual void add(std::function func, bool Sequential = false) = 0; + virtual size_t getThreadCount() const = 0; static Executor *getDefaultExecutor(); }; @@ -49,7 +50,7 @@ class ThreadPoolExecutor : public Executor { public: explicit ThreadPoolExecutor(ThreadPoolStrategy S = hardware_concurrency()) { - unsigned ThreadCount = S.compute_thread_count(); + ThreadCount = S.compute_thread_count(); // Spawn all but one of the threads in another thread as spawning threads // can take a while. Threads.reserve(ThreadCount); @@ -58,7 +59,7 @@ // Use operator[] before creating the thread to avoid data race in .size() // in “safe libc++” mode. auto &Thread0 = Threads[0]; - Thread0 = std::thread([this, ThreadCount, S] { + Thread0 = std::thread([this, S] { for (unsigned I = 1; I < ThreadCount; ++I) { Threads.emplace_back([=] { work(S, I); }); if (Stop) @@ -108,6 +109,8 @@ Cond.notify_one(); } + size_t getThreadCount() const override { return ThreadCount; } + private: bool hasSequentialTasks() const { return !WorkQueueSequential.empty() && !SequentialQueueIsLocked; @@ -149,6 +152,7 @@ std::condition_variable Cond; std::promise ThreadsCreated; std::vector Threads; + unsigned ThreadCount; }; Executor *Executor::getDefaultExecutor() { @@ -178,6 +182,10 @@ } } // namespace } // namespace detail + +size_t getThreadCount() { + return detail::Executor::getDefaultExecutor()->getThreadCount(); +} #endif // Latch::sync() called by the dtor may cause one thread to block. If is a dead diff --git a/llvm/unittests/ADT/ConcurrentHashtableTest.cpp b/llvm/unittests/ADT/ConcurrentHashtableTest.cpp --- a/llvm/unittests/ADT/ConcurrentHashtableTest.cpp +++ b/llvm/unittests/ADT/ConcurrentHashtableTest.cpp @@ -10,11 +10,13 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Parallel.h" +#include "llvm/Support/PerThreadBumpPtrAllocator.h" #include "gtest/gtest.h" #include #include #include using namespace llvm; +using namespace parallel; namespace { class String { @@ -36,186 +38,195 @@ std::array ExtraData; }; -class SimpleAllocator : public AllocatorBase { -public: - inline LLVM_ATTRIBUTE_RETURNS_NONNULL void *Allocate(size_t Size, - size_t Alignment) { -#if LLVM_ENABLE_THREADS - std::lock_guard Guard(AllocatorMutex); -#endif - - return Allocator.Allocate(Size, Align(Alignment)); - } - inline size_t getBytesAllocated() { -#if LLVM_ENABLE_THREADS - std::lock_guard Guard(AllocatorMutex); -#endif - - return Allocator.getBytesAllocated(); - } - - // Pull in base class overloads. - using AllocatorBase::Allocate; - -protected: -#if LLVM_ENABLE_THREADS - std::mutex AllocatorMutex; -#endif - BumpPtrAllocator Allocator; -} Allocator; - TEST(ConcurrentHashTableTest, AddStringEntries) { - ConcurrentHashTableByPtr< - std::string, String, SimpleAllocator, - ConcurrentHashTableInfoByPtr> + PerThreadBumpPtrAllocator Allocator; + ConcurrentHashTableByPtr> HashTable(Allocator, 10); - size_t AllocatedBytesAtStart = Allocator.getBytesAllocated(); - std::pair res1 = HashTable.insert("1"); - // Check entry is inserted. - EXPECT_TRUE(res1.first->getKey() == "1"); - EXPECT_TRUE(res1.second); - - std::pair res2 = HashTable.insert("2"); - // Check old entry is still valid. - EXPECT_TRUE(res1.first->getKey() == "1"); - // Check new entry is inserted. - EXPECT_TRUE(res2.first->getKey() == "2"); - EXPECT_TRUE(res2.second); - // Check new and old entries use different memory. - EXPECT_TRUE(res1.first != res2.first); - - std::pair res3 = HashTable.insert("3"); - // Check one more entry is inserted. - EXPECT_TRUE(res3.first->getKey() == "3"); - EXPECT_TRUE(res3.second); - - std::pair res4 = HashTable.insert("1"); - // Check duplicated entry is inserted. - EXPECT_TRUE(res4.first->getKey() == "1"); - EXPECT_FALSE(res4.second); - // Check duplicated entry uses the same memory. - EXPECT_TRUE(res1.first == res4.first); - - // Check first entry is still valid. - EXPECT_TRUE(res1.first->getKey() == "1"); - - // Check data was allocated by allocator. - EXPECT_TRUE(Allocator.getBytesAllocated() > AllocatedBytesAtStart); + // PerThreadBumpPtrAllocator should be accessed from threads created by + // ThreadPoolExecutor. Use TaskGroup to run on ThreadPoolExecutor threads. + parallel::TaskGroup tg; - // Check statistic. - std::string StatisticString; - raw_string_ostream StatisticStream(StatisticString); - HashTable.printStatistic(StatisticStream); + tg.spawn([&]() { + size_t AllocatedBytesAtStart = Allocator.getBytesAllocated(); + std::pair res1 = HashTable.insert("1"); + // Check entry is inserted. + EXPECT_TRUE(res1.first->getKey() == "1"); + EXPECT_TRUE(res1.second); + + std::pair res2 = HashTable.insert("2"); + // Check old entry is still valid. + EXPECT_TRUE(res1.first->getKey() == "1"); + // Check new entry is inserted. + EXPECT_TRUE(res2.first->getKey() == "2"); + EXPECT_TRUE(res2.second); + // Check new and old entries use different memory. + EXPECT_TRUE(res1.first != res2.first); + + std::pair res3 = HashTable.insert("3"); + // Check one more entry is inserted. + EXPECT_TRUE(res3.first->getKey() == "3"); + EXPECT_TRUE(res3.second); + + std::pair res4 = HashTable.insert("1"); + // Check duplicated entry is inserted. + EXPECT_TRUE(res4.first->getKey() == "1"); + EXPECT_FALSE(res4.second); + // Check duplicated entry uses the same memory. + EXPECT_TRUE(res1.first == res4.first); + + // Check first entry is still valid. + EXPECT_TRUE(res1.first->getKey() == "1"); + + // Check data was allocated by allocator. + EXPECT_TRUE(Allocator.getBytesAllocated() > AllocatedBytesAtStart); - EXPECT_TRUE(StatisticString.find("Overall number of entries = 3\n") != - std::string::npos); + // Check statistic. + std::string StatisticString; + raw_string_ostream StatisticStream(StatisticString); + HashTable.printStatistic(StatisticStream); + + EXPECT_TRUE(StatisticString.find("Overall number of entries = 3\n") != + std::string::npos); + }); } TEST(ConcurrentHashTableTest, AddStringMultiplueEntries) { + PerThreadBumpPtrAllocator Allocator; const size_t NumElements = 10000; - ConcurrentHashTableByPtr< - std::string, String, SimpleAllocator, - ConcurrentHashTableInfoByPtr> + ConcurrentHashTableByPtr> HashTable(Allocator); - // Check insertion. - for (size_t I = 0; I < NumElements; I++) { - size_t AllocatedBytesAtStart = Allocator.getBytesAllocated(); - std::string StringForElement = formatv("{0}", I); - std::pair Entry = HashTable.insert(StringForElement); - EXPECT_TRUE(Entry.second); - EXPECT_TRUE(Entry.first->getKey() == StringForElement); - EXPECT_TRUE(Allocator.getBytesAllocated() > AllocatedBytesAtStart); - } - - std::string StatisticString; - raw_string_ostream StatisticStream(StatisticString); - HashTable.printStatistic(StatisticStream); - - // Verifying that the table contains exactly the number of elements we - // inserted. - EXPECT_TRUE(StatisticString.find("Overall number of entries = 10000\n") != - std::string::npos); - - // Check insertion of duplicates. - for (size_t I = 0; I < NumElements; I++) { - size_t AllocatedBytesAtStart = Allocator.getBytesAllocated(); - std::string StringForElement = formatv("{0}", I); - std::pair Entry = HashTable.insert(StringForElement); - EXPECT_FALSE(Entry.second); - EXPECT_TRUE(Entry.first->getKey() == StringForElement); - // Check no additional bytes were allocated for duplicate. - EXPECT_TRUE(Allocator.getBytesAllocated() == AllocatedBytesAtStart); - } - - // Check statistic. - // Verifying that the table contains exactly the number of elements we - // inserted. - EXPECT_TRUE(StatisticString.find("Overall number of entries = 10000\n") != - std::string::npos); + // PerThreadBumpPtrAllocator should be accessed from threads created by + // ThreadPoolExecutor. Use TaskGroup to run on ThreadPoolExecutor threads. + parallel::TaskGroup tg; + + tg.spawn([&]() { + // Check insertion. + for (size_t I = 0; I < NumElements; I++) { + BumpPtrAllocator &ThreadLocalAllocator = + Allocator.getThreadLocalAllocator(); + size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated(); + std::string StringForElement = formatv("{0}", I); + std::pair Entry = HashTable.insert(StringForElement); + EXPECT_TRUE(Entry.second); + EXPECT_TRUE(Entry.first->getKey() == StringForElement); + EXPECT_TRUE(ThreadLocalAllocator.getBytesAllocated() > + AllocatedBytesAtStart); + } + + std::string StatisticString; + raw_string_ostream StatisticStream(StatisticString); + HashTable.printStatistic(StatisticStream); + + // Verifying that the table contains exactly the number of elements we + // inserted. + EXPECT_TRUE(StatisticString.find("Overall number of entries = 10000\n") != + std::string::npos); + + // Check insertion of duplicates. + for (size_t I = 0; I < NumElements; I++) { + BumpPtrAllocator &ThreadLocalAllocator = + Allocator.getThreadLocalAllocator(); + size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated(); + std::string StringForElement = formatv("{0}", I); + std::pair Entry = HashTable.insert(StringForElement); + EXPECT_FALSE(Entry.second); + EXPECT_TRUE(Entry.first->getKey() == StringForElement); + // Check no additional bytes were allocated for duplicate. + EXPECT_TRUE(ThreadLocalAllocator.getBytesAllocated() == + AllocatedBytesAtStart); + } + + // Check statistic. + // Verifying that the table contains exactly the number of elements we + // inserted. + EXPECT_TRUE(StatisticString.find("Overall number of entries = 10000\n") != + std::string::npos); + }); } TEST(ConcurrentHashTableTest, AddStringMultiplueEntriesWithResize) { + PerThreadBumpPtrAllocator Allocator; // Number of elements exceeds original size, thus hashtable should be resized. const size_t NumElements = 20000; - ConcurrentHashTableByPtr< - std::string, String, SimpleAllocator, - ConcurrentHashTableInfoByPtr> + ConcurrentHashTableByPtr> HashTable(Allocator, 100); - // Check insertion. - for (size_t I = 0; I < NumElements; I++) { - size_t AllocatedBytesAtStart = Allocator.getBytesAllocated(); - std::string StringForElement = formatv("{0} {1}", I, I + 100); - std::pair Entry = HashTable.insert(StringForElement); - EXPECT_TRUE(Entry.second); - EXPECT_TRUE(Entry.first->getKey() == StringForElement); - EXPECT_TRUE(Allocator.getBytesAllocated() > AllocatedBytesAtStart); - } - - std::string StatisticString; - raw_string_ostream StatisticStream(StatisticString); - HashTable.printStatistic(StatisticStream); - - // Verifying that the table contains exactly the number of elements we - // inserted. - EXPECT_TRUE(StatisticString.find("Overall number of entries = 20000\n") != - std::string::npos); - - // Check insertion of duplicates. - for (size_t I = 0; I < NumElements; I++) { - size_t AllocatedBytesAtStart = Allocator.getBytesAllocated(); - std::string StringForElement = formatv("{0} {1}", I, I + 100); - std::pair Entry = HashTable.insert(StringForElement); - EXPECT_FALSE(Entry.second); - EXPECT_TRUE(Entry.first->getKey() == StringForElement); - // Check no additional bytes were allocated for duplicate. - EXPECT_TRUE(Allocator.getBytesAllocated() == AllocatedBytesAtStart); - } - - // Check statistic. - // Verifying that the table contains exactly the number of elements we - // inserted. - EXPECT_TRUE(StatisticString.find("Overall number of entries = 20000\n") != - std::string::npos); + // PerThreadBumpPtrAllocator should be accessed from threads created by + // ThreadPoolExecutor. Use TaskGroup to run on ThreadPoolExecutor threads. + parallel::TaskGroup tg; + + tg.spawn([&]() { + // Check insertion. + for (size_t I = 0; I < NumElements; I++) { + BumpPtrAllocator &ThreadLocalAllocator = + Allocator.getThreadLocalAllocator(); + size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated(); + std::string StringForElement = formatv("{0} {1}", I, I + 100); + std::pair Entry = HashTable.insert(StringForElement); + EXPECT_TRUE(Entry.second); + EXPECT_TRUE(Entry.first->getKey() == StringForElement); + EXPECT_TRUE(ThreadLocalAllocator.getBytesAllocated() > + AllocatedBytesAtStart); + } + + std::string StatisticString; + raw_string_ostream StatisticStream(StatisticString); + HashTable.printStatistic(StatisticStream); + + // Verifying that the table contains exactly the number of elements we + // inserted. + EXPECT_TRUE(StatisticString.find("Overall number of entries = 20000\n") != + std::string::npos); + + // Check insertion of duplicates. + for (size_t I = 0; I < NumElements; I++) { + BumpPtrAllocator &ThreadLocalAllocator = + Allocator.getThreadLocalAllocator(); + size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated(); + std::string StringForElement = formatv("{0} {1}", I, I + 100); + std::pair Entry = HashTable.insert(StringForElement); + EXPECT_FALSE(Entry.second); + EXPECT_TRUE(Entry.first->getKey() == StringForElement); + // Check no additional bytes were allocated for duplicate. + EXPECT_TRUE(ThreadLocalAllocator.getBytesAllocated() == + AllocatedBytesAtStart); + } + + // Check statistic. + // Verifying that the table contains exactly the number of elements we + // inserted. + EXPECT_TRUE(StatisticString.find("Overall number of entries = 20000\n") != + std::string::npos); + }); } TEST(ConcurrentHashTableTest, AddStringEntriesParallel) { + PerThreadBumpPtrAllocator Allocator; const size_t NumElements = 10000; - ConcurrentHashTableByPtr< - std::string, String, SimpleAllocator, - ConcurrentHashTableInfoByPtr> + ConcurrentHashTableByPtr> HashTable(Allocator); // Check parallel insertion. parallelFor(0, NumElements, [&](size_t I) { - size_t AllocatedBytesAtStart = Allocator.getBytesAllocated(); + BumpPtrAllocator &ThreadLocalAllocator = + Allocator.getThreadLocalAllocator(); + size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated(); std::string StringForElement = formatv("{0}", I); std::pair Entry = HashTable.insert(StringForElement); EXPECT_TRUE(Entry.second); EXPECT_TRUE(Entry.first->getKey() == StringForElement); - EXPECT_TRUE(Allocator.getBytesAllocated() > AllocatedBytesAtStart); + EXPECT_TRUE(ThreadLocalAllocator.getBytesAllocated() > + AllocatedBytesAtStart); }); std::string StatisticString; @@ -229,13 +240,16 @@ // Check parallel insertion of duplicates. parallelFor(0, NumElements, [&](size_t I) { - size_t AllocatedBytesAtStart = Allocator.getBytesAllocated(); + BumpPtrAllocator &ThreadLocalAllocator = + Allocator.getThreadLocalAllocator(); + size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated(); std::string StringForElement = formatv("{0}", I); std::pair Entry = HashTable.insert(StringForElement); EXPECT_FALSE(Entry.second); EXPECT_TRUE(Entry.first->getKey() == StringForElement); // Check no additional bytes were allocated for duplicate. - EXPECT_TRUE(Allocator.getBytesAllocated() == AllocatedBytesAtStart); + EXPECT_TRUE(ThreadLocalAllocator.getBytesAllocated() == + AllocatedBytesAtStart); }); // Check statistic. @@ -246,20 +260,24 @@ } TEST(ConcurrentHashTableTest, AddStringEntriesParallelWithResize) { + PerThreadBumpPtrAllocator Allocator; const size_t NumElements = 20000; - ConcurrentHashTableByPtr< - std::string, String, SimpleAllocator, - ConcurrentHashTableInfoByPtr> + ConcurrentHashTableByPtr> HashTable(Allocator, 100); // Check parallel insertion. parallelFor(0, NumElements, [&](size_t I) { - size_t AllocatedBytesAtStart = Allocator.getBytesAllocated(); + BumpPtrAllocator &ThreadLocalAllocator = + Allocator.getThreadLocalAllocator(); + size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated(); std::string StringForElement = formatv("{0}", I); std::pair Entry = HashTable.insert(StringForElement); EXPECT_TRUE(Entry.second); EXPECT_TRUE(Entry.first->getKey() == StringForElement); - EXPECT_TRUE(Allocator.getBytesAllocated() > AllocatedBytesAtStart); + EXPECT_TRUE(ThreadLocalAllocator.getBytesAllocated() > + AllocatedBytesAtStart); }); std::string StatisticString; @@ -273,13 +291,16 @@ // Check parallel insertion of duplicates. parallelFor(0, NumElements, [&](size_t I) { - size_t AllocatedBytesAtStart = Allocator.getBytesAllocated(); + BumpPtrAllocator &ThreadLocalAllocator = + Allocator.getThreadLocalAllocator(); + size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated(); std::string StringForElement = formatv("{0}", I); std::pair Entry = HashTable.insert(StringForElement); EXPECT_FALSE(Entry.second); EXPECT_TRUE(Entry.first->getKey() == StringForElement); // Check no additional bytes were allocated for duplicate. - EXPECT_TRUE(Allocator.getBytesAllocated() == AllocatedBytesAtStart); + EXPECT_TRUE(ThreadLocalAllocator.getBytesAllocated() == + AllocatedBytesAtStart); }); // Check statistic. diff --git a/llvm/unittests/DWARFLinkerParallel/StringPoolTest.cpp b/llvm/unittests/DWARFLinkerParallel/StringPoolTest.cpp --- a/llvm/unittests/DWARFLinkerParallel/StringPoolTest.cpp +++ b/llvm/unittests/DWARFLinkerParallel/StringPoolTest.cpp @@ -19,24 +19,31 @@ TEST(StringPoolTest, TestStringPool) { StringPool Strings; - std::pair Entry = Strings.insert("test"); - EXPECT_TRUE(Entry.second); - EXPECT_TRUE(Entry.first->getKey() == "test"); - EXPECT_TRUE(Entry.first->second == nullptr); - - StringEntry *EntryPtr = Entry.first; - - Entry = Strings.insert("test"); - EXPECT_FALSE(Entry.second); - EXPECT_TRUE(Entry.first->getKey() == "test"); - EXPECT_TRUE(Entry.first->second == nullptr); - EXPECT_TRUE(EntryPtr == Entry.first); - - Entry = Strings.insert("test2"); - EXPECT_TRUE(Entry.second); - EXPECT_TRUE(Entry.first->getKey() == "test2"); - EXPECT_TRUE(Entry.first->second == nullptr); - EXPECT_TRUE(EntryPtr != Entry.first); + // StringPool uses PerThreadBumpPtrAllocator which should be accessed from + // threads created by ThreadPoolExecutor. Use TaskGroup to run on + // ThreadPoolExecutor threads. + parallel::TaskGroup tg; + + tg.spawn([&]() { + std::pair Entry = Strings.insert("test"); + EXPECT_TRUE(Entry.second); + EXPECT_TRUE(Entry.first->getKey() == "test"); + EXPECT_TRUE(Entry.first->second == nullptr); + + StringEntry *EntryPtr = Entry.first; + + Entry = Strings.insert("test"); + EXPECT_FALSE(Entry.second); + EXPECT_TRUE(Entry.first->getKey() == "test"); + EXPECT_TRUE(Entry.first->second == nullptr); + EXPECT_TRUE(EntryPtr == Entry.first); + + Entry = Strings.insert("test2"); + EXPECT_TRUE(Entry.second); + EXPECT_TRUE(Entry.first->getKey() == "test2"); + EXPECT_TRUE(Entry.first->second == nullptr); + EXPECT_TRUE(EntryPtr != Entry.first); + }); } TEST(StringPoolTest, TestStringPoolParallel) { diff --git a/llvm/unittests/DWARFLinkerParallel/StringTableTest.cpp b/llvm/unittests/DWARFLinkerParallel/StringTableTest.cpp --- a/llvm/unittests/DWARFLinkerParallel/StringTableTest.cpp +++ b/llvm/unittests/DWARFLinkerParallel/StringTableTest.cpp @@ -29,41 +29,48 @@ StringPool Strings; StringTable OutStrings(Strings, nullptr); - // Check string insertion. - StringEntry *FirstPtr = Strings.insert(InputStrings[0].Str).first; - StringEntry *SecondPtr = Strings.insert(InputStrings[1].Str).first; - StringEntry *ThirdPtr = Strings.insert(InputStrings[2].Str).first; - - FirstPtr = OutStrings.add(FirstPtr); - SecondPtr = OutStrings.add(SecondPtr); - ThirdPtr = OutStrings.add(ThirdPtr); - - // Check fields of inserted strings. - EXPECT_TRUE(FirstPtr->getKey() == InputStrings[0].Str); - EXPECT_TRUE(FirstPtr->getValue()->Offset == InputStrings[0].Offset); - EXPECT_TRUE(FirstPtr->getValue()->Index == InputStrings[0].Idx); - - EXPECT_TRUE(SecondPtr->getKey() == InputStrings[1].Str); - EXPECT_TRUE(SecondPtr->getValue()->Offset == InputStrings[1].Offset); - EXPECT_TRUE(SecondPtr->getValue()->Index == InputStrings[1].Idx); - - EXPECT_TRUE(ThirdPtr->getKey() == InputStrings[2].Str); - EXPECT_TRUE(ThirdPtr->getValue()->Offset == InputStrings[2].Offset); - EXPECT_TRUE(ThirdPtr->getValue()->Index == InputStrings[2].Idx); - - // Check order enumerated strings. - uint64_t CurIdx = 0; - std::function checkStr = - [&](DwarfStringPoolEntryRef Entry) { - EXPECT_TRUE(Entry.getEntry().isIndexed()); - EXPECT_TRUE(Entry.getIndex() == CurIdx); - EXPECT_TRUE(Entry.getOffset() == InputStrings[CurIdx].Offset); - EXPECT_TRUE(Entry.getString() == InputStrings[CurIdx].Str); - - CurIdx++; - }; - - OutStrings.forEach(checkStr); + // StringPool uses PerThreadBumpPtrAllocator which should be accessed from + // threads created by ThreadPoolExecutor. Use TaskGroup to run on + // ThreadPoolExecutor threads. + parallel::TaskGroup tg; + + tg.spawn([&]() { + // Check string insertion. + StringEntry *FirstPtr = Strings.insert(InputStrings[0].Str).first; + StringEntry *SecondPtr = Strings.insert(InputStrings[1].Str).first; + StringEntry *ThirdPtr = Strings.insert(InputStrings[2].Str).first; + + FirstPtr = OutStrings.add(FirstPtr); + SecondPtr = OutStrings.add(SecondPtr); + ThirdPtr = OutStrings.add(ThirdPtr); + + // Check fields of inserted strings. + EXPECT_TRUE(FirstPtr->getKey() == InputStrings[0].Str); + EXPECT_TRUE(FirstPtr->getValue()->Offset == InputStrings[0].Offset); + EXPECT_TRUE(FirstPtr->getValue()->Index == InputStrings[0].Idx); + + EXPECT_TRUE(SecondPtr->getKey() == InputStrings[1].Str); + EXPECT_TRUE(SecondPtr->getValue()->Offset == InputStrings[1].Offset); + EXPECT_TRUE(SecondPtr->getValue()->Index == InputStrings[1].Idx); + + EXPECT_TRUE(ThirdPtr->getKey() == InputStrings[2].Str); + EXPECT_TRUE(ThirdPtr->getValue()->Offset == InputStrings[2].Offset); + EXPECT_TRUE(ThirdPtr->getValue()->Index == InputStrings[2].Idx); + + // Check order enumerated strings. + uint64_t CurIdx = 0; + std::function checkStr = + [&](DwarfStringPoolEntryRef Entry) { + EXPECT_TRUE(Entry.getEntry().isIndexed()); + EXPECT_TRUE(Entry.getIndex() == CurIdx); + EXPECT_TRUE(Entry.getOffset() == InputStrings[CurIdx].Offset); + EXPECT_TRUE(Entry.getString() == InputStrings[CurIdx].Str); + + CurIdx++; + }; + + OutStrings.forEach(checkStr); + }); } TEST(StringPoolTest, TestStringTableWithTranslator) { @@ -80,25 +87,32 @@ StringPool Strings; StringTable OutStrings(Strings, TranslatorFunc); - StringEntry *FirstPtr = Strings.insert("first").first; - StringEntry *SecondPtr = Strings.insert("second").first; - StringEntry *ThirdPtr = Strings.insert("third").first; + // StringPool uses PerThreadBumpPtrAllocator which should be accessed from + // threads created by ThreadPoolExecutor. Use TaskGroup to run on + // ThreadPoolExecutor threads. + parallel::TaskGroup tg; - FirstPtr = OutStrings.add(FirstPtr); - SecondPtr = OutStrings.add(SecondPtr); - ThirdPtr = OutStrings.add(ThirdPtr); + tg.spawn([&]() { + StringEntry *FirstPtr = Strings.insert("first").first; + StringEntry *SecondPtr = Strings.insert("second").first; + StringEntry *ThirdPtr = Strings.insert("third").first; - EXPECT_TRUE(FirstPtr->getKey() == "tsrif0"); - EXPECT_TRUE(FirstPtr->getValue()->Offset == 0); - EXPECT_TRUE(FirstPtr->getValue()->Index == 0); + FirstPtr = OutStrings.add(FirstPtr); + SecondPtr = OutStrings.add(SecondPtr); + ThirdPtr = OutStrings.add(ThirdPtr); - EXPECT_TRUE(SecondPtr->getKey() == "dnoces0"); - EXPECT_TRUE(SecondPtr->getValue()->Offset == 7); - EXPECT_TRUE(SecondPtr->getValue()->Index == 1); + EXPECT_TRUE(FirstPtr->getKey() == "tsrif0"); + EXPECT_TRUE(FirstPtr->getValue()->Offset == 0); + EXPECT_TRUE(FirstPtr->getValue()->Index == 0); - EXPECT_TRUE(ThirdPtr->getKey() == "driht0"); - EXPECT_TRUE(ThirdPtr->getValue()->Offset == 15); - EXPECT_TRUE(ThirdPtr->getValue()->Index == 2); + EXPECT_TRUE(SecondPtr->getKey() == "dnoces0"); + EXPECT_TRUE(SecondPtr->getValue()->Offset == 7); + EXPECT_TRUE(SecondPtr->getValue()->Index == 1); + + EXPECT_TRUE(ThirdPtr->getKey() == "driht0"); + EXPECT_TRUE(ThirdPtr->getValue()->Offset == 15); + EXPECT_TRUE(ThirdPtr->getValue()->Index == 2); + }); } } // anonymous namespace diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -63,6 +63,7 @@ OptimizedStructLayoutTest.cpp ParallelTest.cpp Path.cpp + PerThreadBumpPtrAllocatorTest.cpp ProcessTest.cpp ProgramTest.cpp RegexTest.cpp diff --git a/llvm/unittests/Support/PerThreadBumpPtrAllocatorTest.cpp b/llvm/unittests/Support/PerThreadBumpPtrAllocatorTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Support/PerThreadBumpPtrAllocatorTest.cpp @@ -0,0 +1,56 @@ +//===- PerThreadBumpPtrAllocatorTest.cpp ----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/PerThreadBumpPtrAllocator.h" +#include "llvm/Support/Parallel.h" +#include "gtest/gtest.h" +#include + +using namespace llvm; +using namespace parallel; + +namespace { + +TEST(PerThreadBumpPtrAllocatorTest, Simple) { + PerThreadBumpPtrAllocator Allocator; + + parallel::TaskGroup tg; + + tg.spawn([&]() { + uint64_t *Var = + (uint64_t *)Allocator.Allocate(sizeof(uint64_t), alignof(uint64_t)); + *Var = 0xFE; + EXPECT_EQ(0xFEul, *Var); + EXPECT_EQ(sizeof(uint64_t), Allocator.getBytesAllocated()); + EXPECT_TRUE(Allocator.getBytesAllocated() <= Allocator.getTotalMemory()); + + PerThreadBumpPtrAllocator Allocator2(std::move(Allocator)); + + EXPECT_EQ(sizeof(uint64_t), Allocator2.getBytesAllocated()); + EXPECT_TRUE(Allocator2.getBytesAllocated() <= Allocator2.getTotalMemory()); + + EXPECT_EQ(0xFEul, *Var); + }); +} + +TEST(PerThreadBumpPtrAllocatorTest, ParallelAllocation) { + PerThreadBumpPtrAllocator Allocator; + + static size_t constexpr NumAllocations = 1000; + + parallelFor(0, NumAllocations, [&](size_t Idx) { + uint64_t *ptr = + (uint64_t *)Allocator.Allocate(sizeof(uint64_t), alignof(uint64_t)); + *ptr = Idx; + }); + + EXPECT_EQ(sizeof(uint64_t) * NumAllocations, Allocator.getBytesAllocated()); + EXPECT_EQ(Allocator.getNumberOfAllocators(), parallel::getThreadCount()); +} + +} // anonymous namespace