
PerThreadBumpPtrAllocator allows separating allocations by thread id. That makes allocations race free. It is possible because ThreadPoolExecutor class creates threads, keeps them until the destructor of ThreadPoolExecutor is called, and assigns ids to the threads. Thus PerThreadBumpPtrAllocator should be used with only threads created by ThreadPoolExecutor. This allocator is useful when thread safe BumpPtrAllocator is needed. Reviewed By: MaskRay, dexonsmith, andrewng Differential Revision: https://reviews.llvm.org/D142318
314 lines
12 KiB
C++
314 lines
12 KiB
C++
//===- ConcurrentHashtableTest.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/ADT/ConcurrentHashtable.h"
|
|
#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 <limits>
|
|
#include <random>
|
|
#include <vector>
|
|
using namespace llvm;
|
|
using namespace parallel;
|
|
|
|
namespace {
|
|
class String {
|
|
public:
|
|
String() {}
|
|
const std::string &getKey() const { return Data; }
|
|
|
|
template <typename AllocatorTy>
|
|
static String *create(const std::string &Num, AllocatorTy &Allocator) {
|
|
String *Result = Allocator.template Allocate<String>();
|
|
new (Result) String(Num);
|
|
return Result;
|
|
}
|
|
|
|
protected:
|
|
String(const std::string &Num) { Data += Num; }
|
|
|
|
std::string Data;
|
|
std::array<char, 0x20> ExtraData;
|
|
};
|
|
|
|
TEST(ConcurrentHashTableTest, AddStringEntries) {
|
|
PerThreadBumpPtrAllocator Allocator;
|
|
ConcurrentHashTableByPtr<std::string, String, PerThreadBumpPtrAllocator,
|
|
ConcurrentHashTableInfoByPtr<
|
|
std::string, String, PerThreadBumpPtrAllocator>>
|
|
HashTable(Allocator, 10);
|
|
|
|
// PerThreadBumpPtrAllocator should be accessed from threads created by
|
|
// ThreadPoolExecutor. Use TaskGroup to run on ThreadPoolExecutor threads.
|
|
parallel::TaskGroup tg;
|
|
|
|
tg.spawn([&]() {
|
|
size_t AllocatedBytesAtStart = Allocator.getBytesAllocated();
|
|
std::pair<String *, bool> res1 = HashTable.insert("1");
|
|
// Check entry is inserted.
|
|
EXPECT_TRUE(res1.first->getKey() == "1");
|
|
EXPECT_TRUE(res1.second);
|
|
|
|
std::pair<String *, bool> 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<String *, bool> res3 = HashTable.insert("3");
|
|
// Check one more entry is inserted.
|
|
EXPECT_TRUE(res3.first->getKey() == "3");
|
|
EXPECT_TRUE(res3.second);
|
|
|
|
std::pair<String *, bool> 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);
|
|
|
|
// 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, PerThreadBumpPtrAllocator,
|
|
ConcurrentHashTableInfoByPtr<
|
|
std::string, String, PerThreadBumpPtrAllocator>>
|
|
HashTable(Allocator);
|
|
|
|
// 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<String *, bool> 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<String *, bool> 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, PerThreadBumpPtrAllocator,
|
|
ConcurrentHashTableInfoByPtr<
|
|
std::string, String, PerThreadBumpPtrAllocator>>
|
|
HashTable(Allocator, 100);
|
|
|
|
// 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<String *, bool> 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<String *, bool> 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, PerThreadBumpPtrAllocator,
|
|
ConcurrentHashTableInfoByPtr<
|
|
std::string, String, PerThreadBumpPtrAllocator>>
|
|
HashTable(Allocator);
|
|
|
|
// Check parallel insertion.
|
|
parallelFor(0, NumElements, [&](size_t I) {
|
|
BumpPtrAllocator &ThreadLocalAllocator =
|
|
Allocator.getThreadLocalAllocator();
|
|
size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated();
|
|
std::string StringForElement = formatv("{0}", I);
|
|
std::pair<String *, bool> 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 parallel insertion of duplicates.
|
|
parallelFor(0, NumElements, [&](size_t I) {
|
|
BumpPtrAllocator &ThreadLocalAllocator =
|
|
Allocator.getThreadLocalAllocator();
|
|
size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated();
|
|
std::string StringForElement = formatv("{0}", I);
|
|
std::pair<String *, bool> 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, AddStringEntriesParallelWithResize) {
|
|
PerThreadBumpPtrAllocator Allocator;
|
|
const size_t NumElements = 20000;
|
|
ConcurrentHashTableByPtr<std::string, String, PerThreadBumpPtrAllocator,
|
|
ConcurrentHashTableInfoByPtr<
|
|
std::string, String, PerThreadBumpPtrAllocator>>
|
|
HashTable(Allocator, 100);
|
|
|
|
// Check parallel insertion.
|
|
parallelFor(0, NumElements, [&](size_t I) {
|
|
BumpPtrAllocator &ThreadLocalAllocator =
|
|
Allocator.getThreadLocalAllocator();
|
|
size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated();
|
|
std::string StringForElement = formatv("{0}", I);
|
|
std::pair<String *, bool> 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 parallel insertion of duplicates.
|
|
parallelFor(0, NumElements, [&](size_t I) {
|
|
BumpPtrAllocator &ThreadLocalAllocator =
|
|
Allocator.getThreadLocalAllocator();
|
|
size_t AllocatedBytesAtStart = ThreadLocalAllocator.getBytesAllocated();
|
|
std::string StringForElement = formatv("{0}", I);
|
|
std::pair<String *, bool> 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);
|
|
}
|
|
|
|
} // namespace
|