Apply clang-format (Chromium) (#13)

This commit is contained in:
Terry Kim 2019-07-13 20:19:07 -07:00 коммит произвёл GitHub
Родитель 32a7737afe
Коммит 64e70ac102
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
64 изменённых файлов: 5013 добавлений и 5896 удалений

2
.gitignore поставляемый
Просмотреть файл

@ -67,3 +67,5 @@ ipch/
*.vsp *.vsp
*.vspx *.vspx
*.sap *.sap
*.htm
*.user

Просмотреть файл

@ -2,91 +2,73 @@
#include "L4/Log/PerfCounter.h" #include "L4/Log/PerfCounter.h"
#include <algorithm> #include <algorithm>
#include <boost/any.hpp>
#include <boost/program_options.hpp>
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <thread> #include <thread>
#include <boost/any.hpp>
#include <boost/program_options.hpp>
class Timer class Timer {
{ public:
public: Timer() : m_start{std::chrono::high_resolution_clock::now()} {}
Timer()
: m_start{ std::chrono::high_resolution_clock::now() }
{}
void Reset() void Reset() { m_start = std::chrono::high_resolution_clock::now(); }
{
m_start = std::chrono::high_resolution_clock::now();
}
std::chrono::microseconds GetElapsedTime() std::chrono::microseconds GetElapsedTime() {
{
return std::chrono::duration_cast<std::chrono::microseconds>( return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now() - m_start); std::chrono::high_resolution_clock::now() - m_start);
} }
private: private:
std::chrono::time_point<std::chrono::steady_clock> m_start; std::chrono::time_point<std::chrono::steady_clock> m_start;
}; };
class SynchronizedTimer {
class SynchronizedTimer public:
{
public:
SynchronizedTimer() = default; SynchronizedTimer() = default;
void Start() void Start() {
{ if (m_isStarted) {
if (m_isStarted)
{
return; return;
} }
m_isStarted = true; m_isStarted = true;
m_startCount = std::chrono::high_resolution_clock::now().time_since_epoch().count(); m_startCount =
std::chrono::high_resolution_clock::now().time_since_epoch().count();
} }
void End() void End() {
{ m_endCount =
m_endCount = std::chrono::high_resolution_clock::now().time_since_epoch().count(); std::chrono::high_resolution_clock::now().time_since_epoch().count();
} }
std::chrono::microseconds GetElapsedTime() std::chrono::microseconds GetElapsedTime() {
{ std::chrono::nanoseconds start{m_startCount};
std::chrono::nanoseconds start{ m_startCount }; std::chrono::nanoseconds end{m_endCount};
std::chrono::nanoseconds end{ m_endCount };
return std::chrono::duration_cast<std::chrono::microseconds>(end - start); return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
} }
private: private:
std::atomic_bool m_isStarted = false; std::atomic_bool m_isStarted = false;
std::atomic_uint64_t m_startCount; std::atomic_uint64_t m_startCount;
std::atomic_uint64_t m_endCount; std::atomic_uint64_t m_endCount;
}; };
struct PerThreadInfoForWriteTest {
struct PerThreadInfoForWriteTest
{
std::thread m_thread; std::thread m_thread;
std::size_t m_dataSetSize = 0; std::size_t m_dataSetSize = 0;
std::chrono::microseconds m_totalTime; std::chrono::microseconds m_totalTime;
}; };
struct PerThreadInfoForReadTest {
struct PerThreadInfoForReadTest
{
std::thread m_thread; std::thread m_thread;
std::size_t m_dataSetSize = 0; std::size_t m_dataSetSize = 0;
std::chrono::microseconds m_totalTime; std::chrono::microseconds m_totalTime;
}; };
struct CommandLineOptions {
struct CommandLineOptions
{
static constexpr std::size_t c_defaultDataSetSize = 1000000; static constexpr std::size_t c_defaultDataSetSize = 1000000;
static constexpr std::uint32_t c_defaultNumBuckets = 1000000; static constexpr std::uint32_t c_defaultNumBuckets = 1000000;
static constexpr std::uint16_t c_defaultKeySize = 16; static constexpr std::uint16_t c_defaultKeySize = 16;
@ -116,29 +98,24 @@ struct CommandLineOptions
std::uint64_t m_cacheSizeInBytes = 0U; std::uint64_t m_cacheSizeInBytes = 0U;
bool m_forceTimeBasedEviction = false; bool m_forceTimeBasedEviction = false;
bool IsCachingModule() const bool IsCachingModule() const {
{ static const std::string c_cachingModulePrefix{"cache"};
static const std::string c_cachingModulePrefix{ "cache" }; return m_module.substr(0, c_cachingModulePrefix.size()) ==
return m_module.substr(0, c_cachingModulePrefix.size()) == c_cachingModulePrefix; c_cachingModulePrefix;
} }
}; };
class DataGenerator {
class DataGenerator public:
{ DataGenerator(std::size_t dataSetSize,
public:
DataGenerator(
std::size_t dataSetSize,
std::uint16_t keySize, std::uint16_t keySize,
std::uint32_t valueSize, std::uint32_t valueSize,
bool randomizeValueSize, bool randomizeValueSize,
bool isDebugMode = false) bool isDebugMode = false)
: m_dataSetSize{ dataSetSize } : m_dataSetSize{dataSetSize}, m_keySize{keySize} {
, m_keySize{ keySize } if (isDebugMode) {
{ std::cout << "Generating data set with size = " << dataSetSize
if (isDebugMode) << std::endl;
{
std::cout << "Generating data set with size = " << dataSetSize << std::endl;
} }
Timer timer; Timer timer;
@ -146,11 +123,11 @@ public:
// Populate keys. // Populate keys.
m_keys.resize(m_dataSetSize); m_keys.resize(m_dataSetSize);
m_keysBuffer.resize(m_dataSetSize); m_keysBuffer.resize(m_dataSetSize);
for (std::size_t i = 0; i < m_dataSetSize; ++i) for (std::size_t i = 0; i < m_dataSetSize; ++i) {
{
m_keysBuffer[i].resize(keySize); m_keysBuffer[i].resize(keySize);
std::generate(m_keysBuffer[i].begin(), m_keysBuffer[i].end(), std::rand); std::generate(m_keysBuffer[i].begin(), m_keysBuffer[i].end(), std::rand);
std::snprintf(reinterpret_cast<char*>(m_keysBuffer[i].data()), keySize, "%llu", i); std::snprintf(reinterpret_cast<char*>(m_keysBuffer[i].data()), keySize,
"%llu", i);
m_keys[i].m_data = m_keysBuffer[i].data(); m_keys[i].m_data = m_keysBuffer[i].data();
m_keys[i].m_size = m_keySize; m_keys[i].m_size = m_keySize;
} }
@ -161,31 +138,28 @@ public:
// Populate values. // Populate values.
m_values.resize(m_dataSetSize); m_values.resize(m_dataSetSize);
std::size_t currentIndex = 0; std::size_t currentIndex = 0;
for (std::size_t i = 0; i < m_dataSetSize; ++i) for (std::size_t i = 0; i < m_dataSetSize; ++i) {
{
m_values[i].m_data = &m_valuesBuffer[currentIndex % c_valuesBufferSize]; m_values[i].m_data = &m_valuesBuffer[currentIndex % c_valuesBufferSize];
m_values[i].m_size = randomizeValueSize ? rand() % valueSize : valueSize; m_values[i].m_size = randomizeValueSize ? rand() % valueSize : valueSize;
currentIndex += valueSize; currentIndex += valueSize;
} }
if (isDebugMode) if (isDebugMode) {
{
std::cout << "Finished generating data in " std::cout << "Finished generating data in "
<< timer.GetElapsedTime().count() << " microseconds" << std::endl; << timer.GetElapsedTime().count() << " microseconds"
<< std::endl;
} }
} }
L4::IReadOnlyHashTable::Key GetKey(std::size_t index) const L4::IReadOnlyHashTable::Key GetKey(std::size_t index) const {
{
return m_keys[index % m_dataSetSize]; return m_keys[index % m_dataSetSize];
} }
L4::IReadOnlyHashTable::Value GetValue(std::size_t index) const L4::IReadOnlyHashTable::Value GetValue(std::size_t index) const {
{
return m_values[index % m_dataSetSize]; return m_values[index % m_dataSetSize];
} }
private: private:
std::size_t m_dataSetSize; std::size_t m_dataSetSize;
std::uint16_t m_keySize; std::uint16_t m_keySize;
@ -197,9 +171,7 @@ private:
std::array<std::uint8_t, c_valuesBufferSize> m_valuesBuffer; std::array<std::uint8_t, c_valuesBufferSize> m_valuesBuffer;
}; };
void PrintHardwareInfo() {
void PrintHardwareInfo()
{
SYSTEM_INFO sysInfo; SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo); GetSystemInfo(&sysInfo);
@ -207,108 +179,100 @@ void PrintHardwareInfo()
printf("Hardware information: \n"); printf("Hardware information: \n");
printf("-------------------------------------\n"); printf("-------------------------------------\n");
printf("%22s | %10u |\n", "OEM ID", sysInfo.dwOemId); printf("%22s | %10u |\n", "OEM ID", sysInfo.dwOemId);
printf("%22s | %10u |\n", "Number of processors", sysInfo.dwNumberOfProcessors); printf("%22s | %10u |\n", "Number of processors",
sysInfo.dwNumberOfProcessors);
printf("%22s | %10u |\n", "Page size", sysInfo.dwPageSize); printf("%22s | %10u |\n", "Page size", sysInfo.dwPageSize);
printf("%22s | %10u |\n", "Processor type", sysInfo.dwProcessorType); printf("%22s | %10u |\n", "Processor type", sysInfo.dwProcessorType);
printf("-------------------------------------\n"); printf("-------------------------------------\n");
printf("\n"); printf("\n");
} }
void PrintOptions(const CommandLineOptions& options) {
void PrintOptions(const CommandLineOptions& options)
{
printf("------------------------------------------------------\n"); printf("------------------------------------------------------\n");
printf("%39s | %10llu |\n", "Data set size", options.m_dataSetSize); printf("%39s | %10llu |\n", "Data set size", options.m_dataSetSize);
printf("%39s | %10lu |\n", "Number of hash table buckets", options.m_numBuckets); printf("%39s | %10lu |\n", "Number of hash table buckets",
options.m_numBuckets);
printf("%39s | %10lu |\n", "Key size", options.m_keySize); printf("%39s | %10lu |\n", "Key size", options.m_keySize);
printf("%39s | %10lu |\n", "Value type", options.m_valueSize); printf("%39s | %10lu |\n", "Value type", options.m_valueSize);
printf("%39s | %10lu |\n", "Number of iterations per GetContext()", options.m_numIterationsPerGetContext); printf("%39s | %10lu |\n", "Number of iterations per GetContext()",
printf("%39s | %10lu |\n", "Epoch processing interval (ms)", options.m_epochProcessingIntervalInMilli); options.m_numIterationsPerGetContext);
printf("%39s | %10lu |\n", "Number of actions queue", options.m_numActionsQueue); printf("%39s | %10lu |\n", "Epoch processing interval (ms)",
options.m_epochProcessingIntervalInMilli);
printf("%39s | %10lu |\n", "Number of actions queue",
options.m_numActionsQueue);
if (options.IsCachingModule()) if (options.IsCachingModule()) {
{ printf("%39s | %10lu |\n", "Record time to live (s)",
printf("%39s | %10lu |\n", "Record time to live (s)", options.m_recordTimeToLiveInSeconds); options.m_recordTimeToLiveInSeconds);
printf("%39s | %10llu |\n", "Cache size in bytes", options.m_cacheSizeInBytes); printf("%39s | %10llu |\n", "Cache size in bytes",
printf("%39s | %10lu |\n", "Force time-based eviction", options.m_forceTimeBasedEviction); options.m_cacheSizeInBytes);
printf("%39s | %10lu |\n", "Force time-based eviction",
options.m_forceTimeBasedEviction);
} }
printf("------------------------------------------------------\n\n"); printf("------------------------------------------------------\n\n");
} }
void PrintHashTableCounters(const L4::HashTablePerfData& perfData) {
void PrintHashTableCounters(const L4::HashTablePerfData& perfData)
{
printf("HashTableCounter:\n"); printf("HashTableCounter:\n");
printf("----------------------------------------------------\n"); printf("----------------------------------------------------\n");
for (auto i = 0; i < static_cast<std::uint16_t>(L4::HashTablePerfCounter::Count); ++i) for (auto i = 0;
{ i < static_cast<std::uint16_t>(L4::HashTablePerfCounter::Count); ++i) {
printf("%35s | %12llu |\n", printf("%35s | %12llu |\n", L4::c_hashTablePerfCounterNames[i],
L4::c_hashTablePerfCounterNames[i],
perfData.Get(static_cast<L4::HashTablePerfCounter>(i))); perfData.Get(static_cast<L4::HashTablePerfCounter>(i)));
} }
printf("----------------------------------------------------\n\n"); printf("----------------------------------------------------\n\n");
} }
L4::HashTableConfig CreateHashTableConfig(const CommandLineOptions& options) {
L4::HashTableConfig CreateHashTableConfig(const CommandLineOptions& options)
{
return L4::HashTableConfig( return L4::HashTableConfig(
"Table1", "Table1", L4::HashTableConfig::Setting{options.m_numBuckets},
L4::HashTableConfig::Setting{ options.m_numBuckets },
options.IsCachingModule() options.IsCachingModule()
? boost::optional<L4::HashTableConfig::Cache>{ ? boost::optional<
L4::HashTableConfig::Cache{ L4::HashTableConfig::Cache>{L4::HashTableConfig::Cache{
options.m_cacheSizeInBytes, options.m_cacheSizeInBytes,
std::chrono::seconds{ options.m_recordTimeToLiveInSeconds }, std::chrono::seconds{options.m_recordTimeToLiveInSeconds},
options.m_forceTimeBasedEviction }} options.m_forceTimeBasedEviction}}
: boost::none); : boost::none);
} }
L4::EpochManagerConfig CreateEpochManagerConfig(
L4::EpochManagerConfig CreateEpochManagerConfig(const CommandLineOptions& options) const CommandLineOptions& options) {
{
return L4::EpochManagerConfig( return L4::EpochManagerConfig(
10000U, 10000U,
std::chrono::milliseconds(options.m_epochProcessingIntervalInMilli), std::chrono::milliseconds(options.m_epochProcessingIntervalInMilli),
options.m_numActionsQueue); options.m_numActionsQueue);
} }
void ReadPerfTest(const CommandLineOptions& options) {
void ReadPerfTest(const CommandLineOptions& options)
{
printf("Performing read-perf which reads all the records inserted:\n"); printf("Performing read-perf which reads all the records inserted:\n");
PrintOptions(options); PrintOptions(options);
auto dataGenerator = std::make_unique<DataGenerator>( auto dataGenerator = std::make_unique<DataGenerator>(
options.m_dataSetSize, options.m_dataSetSize, options.m_keySize, options.m_valueSize,
options.m_keySize,
options.m_valueSize,
options.m_randomizeValueSize); options.m_randomizeValueSize);
L4::LocalMemory::HashTableService service(CreateEpochManagerConfig(options)); L4::LocalMemory::HashTableService service(CreateEpochManagerConfig(options));
const auto hashTableIndex = service.AddHashTable(CreateHashTableConfig(options)); const auto hashTableIndex =
service.AddHashTable(CreateHashTableConfig(options));
// Insert data set. // Insert data set.
auto context = service.GetContext(); auto context = service.GetContext();
auto& hashTable = context[hashTableIndex]; auto& hashTable = context[hashTableIndex];
std::vector<std::uint32_t> randomIndices(options.m_dataSetSize); std::vector<std::uint32_t> randomIndices(options.m_dataSetSize);
for (std::uint32_t i = 0U; i < options.m_dataSetSize; ++i) for (std::uint32_t i = 0U; i < options.m_dataSetSize; ++i) {
{
randomIndices[i] = i; randomIndices[i] = i;
} }
if (options.m_numThreads > 0) if (options.m_numThreads > 0) {
{
// Randomize index only if multiple threads are running // Randomize index only if multiple threads are running
// not to skew the results. // not to skew the results.
std::random_shuffle(randomIndices.begin(), randomIndices.end()); std::random_shuffle(randomIndices.begin(), randomIndices.end());
} }
for (int i = 0; i < options.m_dataSetSize; ++i) for (int i = 0; i < options.m_dataSetSize; ++i) {
{
auto key = dataGenerator->GetKey(randomIndices[i]); auto key = dataGenerator->GetKey(randomIndices[i]);
auto val = dataGenerator->GetValue(randomIndices[i]); auto val = dataGenerator->GetValue(randomIndices[i]);
@ -324,9 +288,9 @@ void ReadPerfTest(const CommandLineOptions& options)
const auto isCachingModule = options.IsCachingModule(); const auto isCachingModule = options.IsCachingModule();
bool isReady = false; bool isReady = false;
const std::size_t dataSetSizePerThread = options.m_dataSetSize / options.m_numThreads; const std::size_t dataSetSizePerThread =
for (std::uint16_t i = 0; i < options.m_numThreads; ++i) options.m_dataSetSize / options.m_numThreads;
{ for (std::uint16_t i = 0; i < options.m_numThreads; ++i) {
auto& info = allInfo[i]; auto& info = allInfo[i];
std::size_t startIndex = i * dataSetSizePerThread; std::size_t startIndex = i * dataSetSizePerThread;
@ -334,8 +298,8 @@ void ReadPerfTest(const CommandLineOptions& options)
? options.m_dataSetSize - startIndex ? options.m_dataSetSize - startIndex
: dataSetSizePerThread; : dataSetSizePerThread;
info.m_thread = std::thread([=, &service, &dataGenerator, &info, &mutex, &cv, &isReady, &overallTimer] info.m_thread = std::thread([=, &service, &dataGenerator, &info, &mutex,
{ &cv, &isReady, &overallTimer] {
{ {
std::unique_lock<std::mutex> lock(mutex); std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [&] { return isReady == true; }); cv.wait(lock, [&] { return isReady == true; });
@ -349,19 +313,18 @@ void ReadPerfTest(const CommandLineOptions& options)
std::size_t iteration = 0; std::size_t iteration = 0;
bool isDone = false; bool isDone = false;
while (!isDone) while (!isDone) {
{
auto context = service.GetContext(); auto context = service.GetContext();
auto& hashTable = context[hashTableIndex]; auto& hashTable = context[hashTableIndex];
for (std::uint32_t j = 0; !isDone && j < options.m_numIterationsPerGetContext; ++j) for (std::uint32_t j = 0;
{ !isDone && j < options.m_numIterationsPerGetContext; ++j) {
auto key = dataGenerator->GetKey(startIndex + iteration); auto key = dataGenerator->GetKey(startIndex + iteration);
L4::IReadOnlyHashTable::Value val; L4::IReadOnlyHashTable::Value val;
if (!hashTable.Get(key, val) && !isCachingModule) if (!hashTable.Get(key, val) && !isCachingModule) {
{ throw std::runtime_error(
throw std::runtime_error("Look up failure is not allowed in this test."); "Look up failure is not allowed in this test.");
} }
isDone = (++iteration == info.m_dataSetSize); isDone = (++iteration == info.m_dataSetSize);
@ -382,8 +345,7 @@ void ReadPerfTest(const CommandLineOptions& options)
// Now, start the benchmarking for all threads. // Now, start the benchmarking for all threads.
cv.notify_all(); cv.notify_all();
for (auto& info : allInfo) for (auto& info : allInfo) {
{
info.m_thread.join(); info.m_thread.join();
} }
@ -394,56 +356,46 @@ void ReadPerfTest(const CommandLineOptions& options)
printf(" | micros/op | microseconds | DataSetSize |\n"); printf(" | micros/op | microseconds | DataSetSize |\n");
printf(" -----------------------------------------------------------\n"); printf(" -----------------------------------------------------------\n");
for (std::size_t i = 0; i < allInfo.size(); ++i) for (std::size_t i = 0; i < allInfo.size(); ++i) {
{
const auto& info = allInfo[i]; const auto& info = allInfo[i];
printf(" Thread #%llu | %11.3f | %14llu | %13llu |\n", printf(" Thread #%llu | %11.3f | %14llu | %13llu |\n", (i + 1),
(i + 1),
static_cast<double>(info.m_totalTime.count()) / info.m_dataSetSize, static_cast<double>(info.m_totalTime.count()) / info.m_dataSetSize,
info.m_totalTime.count(), info.m_totalTime.count(), info.m_dataSetSize);
info.m_dataSetSize);
} }
printf(" -----------------------------------------------------------\n"); printf(" -----------------------------------------------------------\n");
printf(" Overall | %11.3f | %14llu | %13llu |\n", printf(" Overall | %11.3f | %14llu | %13llu |\n",
static_cast<double>(overallTimer.GetElapsedTime().count()) / options.m_dataSetSize, static_cast<double>(overallTimer.GetElapsedTime().count()) /
overallTimer.GetElapsedTime().count(), options.m_dataSetSize,
options.m_dataSetSize); overallTimer.GetElapsedTime().count(), options.m_dataSetSize);
} }
void WritePerfTest(const CommandLineOptions& options) {
void WritePerfTest(const CommandLineOptions& options) if (options.m_module == "overwrite-perf") {
{ printf(
if (options.m_module == "overwrite-perf") "Performing overwrite-perf (writing data with unique keys, then "
{ "overwrite data with same keys):\n");
printf("Performing overwrite-perf (writing data with unique keys, then overwrite data with same keys):\n"); } else {
}
else
{
printf("Performing write-perf (writing data with unique keys):\n"); printf("Performing write-perf (writing data with unique keys):\n");
} }
PrintOptions(options); PrintOptions(options);
auto dataGenerator = std::make_unique<DataGenerator>( auto dataGenerator = std::make_unique<DataGenerator>(
options.m_dataSetSize, options.m_dataSetSize, options.m_keySize, options.m_valueSize,
options.m_keySize,
options.m_valueSize,
options.m_randomizeValueSize); options.m_randomizeValueSize);
L4::LocalMemory::HashTableService service(CreateEpochManagerConfig(options)); L4::LocalMemory::HashTableService service(CreateEpochManagerConfig(options));
const auto hashTableIndex = service.AddHashTable(CreateHashTableConfig(options)); const auto hashTableIndex =
service.AddHashTable(CreateHashTableConfig(options));
if (options.m_module == "overwrite-perf") if (options.m_module == "overwrite-perf") {
{
std::vector<std::uint32_t> randomIndices(options.m_dataSetSize); std::vector<std::uint32_t> randomIndices(options.m_dataSetSize);
for (std::uint32_t i = 0U; i < options.m_dataSetSize; ++i) for (std::uint32_t i = 0U; i < options.m_dataSetSize; ++i) {
{
randomIndices[i] = i; randomIndices[i] = i;
} }
if (options.m_numThreads > 0) if (options.m_numThreads > 0) {
{
// Randomize index only if multiple threads are running // Randomize index only if multiple threads are running
// not to skew the results. // not to skew the results.
std::random_shuffle(randomIndices.begin(), randomIndices.end()); std::random_shuffle(randomIndices.begin(), randomIndices.end());
@ -452,8 +404,7 @@ void WritePerfTest(const CommandLineOptions& options)
auto context = service.GetContext(); auto context = service.GetContext();
auto& hashTable = context[hashTableIndex]; auto& hashTable = context[hashTableIndex];
for (int i = 0; i < options.m_dataSetSize; ++i) for (int i = 0; i < options.m_dataSetSize; ++i) {
{
const auto index = randomIndices[i]; const auto index = randomIndices[i];
auto key = dataGenerator->GetKey(index); auto key = dataGenerator->GetKey(index);
auto val = dataGenerator->GetValue(index); auto val = dataGenerator->GetValue(index);
@ -470,9 +421,9 @@ void WritePerfTest(const CommandLineOptions& options)
std::condition_variable cv; std::condition_variable cv;
bool isReady = false; bool isReady = false;
const std::size_t dataSetSizePerThread = options.m_dataSetSize / options.m_numThreads; const std::size_t dataSetSizePerThread =
for (std::uint16_t i = 0; i < options.m_numThreads; ++i) options.m_dataSetSize / options.m_numThreads;
{ for (std::uint16_t i = 0; i < options.m_numThreads; ++i) {
auto& info = allInfo[i]; auto& info = allInfo[i];
std::size_t startIndex = i * dataSetSizePerThread; std::size_t startIndex = i * dataSetSizePerThread;
@ -480,8 +431,8 @@ void WritePerfTest(const CommandLineOptions& options)
? options.m_dataSetSize - startIndex ? options.m_dataSetSize - startIndex
: dataSetSizePerThread; : dataSetSizePerThread;
info.m_thread = std::thread([=, &service, &dataGenerator, &info, &mutex, &cv, &isReady, &overallTimer] info.m_thread = std::thread([=, &service, &dataGenerator, &info, &mutex,
{ &cv, &isReady, &overallTimer] {
{ {
std::unique_lock<std::mutex> lock(mutex); std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [&] { return isReady == true; }); cv.wait(lock, [&] { return isReady == true; });
@ -495,13 +446,12 @@ void WritePerfTest(const CommandLineOptions& options)
std::size_t iteration = 0; std::size_t iteration = 0;
bool isDone = false; bool isDone = false;
while (!isDone) while (!isDone) {
{
auto context = service.GetContext(); auto context = service.GetContext();
auto& hashTable = context[hashTableIndex]; auto& hashTable = context[hashTableIndex];
for (std::uint32_t j = 0; !isDone && j < options.m_numIterationsPerGetContext; ++j) for (std::uint32_t j = 0;
{ !isDone && j < options.m_numIterationsPerGetContext; ++j) {
const auto index = startIndex + iteration; const auto index = startIndex + iteration;
auto key = dataGenerator->GetKey(index); auto key = dataGenerator->GetKey(index);
auto val = dataGenerator->GetValue(index); auto val = dataGenerator->GetValue(index);
@ -525,8 +475,7 @@ void WritePerfTest(const CommandLineOptions& options)
// Now, start the benchmarking for all threads. // Now, start the benchmarking for all threads.
cv.notify_all(); cv.notify_all();
for (auto& info : allInfo) for (auto& info : allInfo) {
{
info.m_thread.join(); info.m_thread.join();
} }
@ -537,69 +486,94 @@ void WritePerfTest(const CommandLineOptions& options)
printf(" | micros/op | microseconds | DataSetSize |\n"); printf(" | micros/op | microseconds | DataSetSize |\n");
printf(" -----------------------------------------------------------\n"); printf(" -----------------------------------------------------------\n");
for (std::size_t i = 0; i < allInfo.size(); ++i) for (std::size_t i = 0; i < allInfo.size(); ++i) {
{
const auto& info = allInfo[i]; const auto& info = allInfo[i];
printf(" Thread #%llu | %11.3f | %14llu | %13llu |\n", printf(" Thread #%llu | %11.3f | %14llu | %13llu |\n", (i + 1),
(i + 1),
static_cast<double>(info.m_totalTime.count()) / info.m_dataSetSize, static_cast<double>(info.m_totalTime.count()) / info.m_dataSetSize,
info.m_totalTime.count(), info.m_totalTime.count(), info.m_dataSetSize);
info.m_dataSetSize);
} }
printf(" -----------------------------------------------------------\n"); printf(" -----------------------------------------------------------\n");
printf(" Overall | %11.3f | %14llu | %13llu |\n", printf(" Overall | %11.3f | %14llu | %13llu |\n",
static_cast<double>(overallTimer.GetElapsedTime().count()) / options.m_dataSetSize, static_cast<double>(overallTimer.GetElapsedTime().count()) /
overallTimer.GetElapsedTime().count(), options.m_dataSetSize,
options.m_dataSetSize); overallTimer.GetElapsedTime().count(), options.m_dataSetSize);
if (options.m_numThreads == 1) if (options.m_numThreads == 1) {
{
auto& perfData = service.GetContext()[hashTableIndex].GetPerfData(); auto& perfData = service.GetContext()[hashTableIndex].GetPerfData();
std::uint64_t totalBytes = perfData.Get(L4::HashTablePerfCounter::TotalKeySize) std::uint64_t totalBytes =
+ perfData.Get(L4::HashTablePerfCounter::TotalValueSize); perfData.Get(L4::HashTablePerfCounter::TotalKeySize) +
perfData.Get(L4::HashTablePerfCounter::TotalValueSize);
auto& info = allInfo[0]; auto& info = allInfo[0];
double opsPerSec = static_cast<double>(info.m_dataSetSize) / info.m_totalTime.count() * 1000000.0; double opsPerSec = static_cast<double>(info.m_dataSetSize) /
double MBPerSec = static_cast<double>(totalBytes) / info.m_totalTime.count(); info.m_totalTime.count() * 1000000.0;
double MBPerSec =
static_cast<double>(totalBytes) / info.m_totalTime.count();
printf(" %10.3f ops/sec %10.3f MB/sec\n", opsPerSec, MBPerSec); printf(" %10.3f ops/sec %10.3f MB/sec\n", opsPerSec, MBPerSec);
} }
} }
CommandLineOptions Parse(int argc, char** argv) {
CommandLineOptions Parse(int argc, char** argv)
{
namespace po = boost::program_options; namespace po = boost::program_options;
po::options_description general("General options"); po::options_description general("General options");
general.add_options() general.add_options()("help", "produce a help message")(
("help", "produce a help message") "help-module", po::value<std::string>(),
("help-module", po::value<std::string>(),
"produce a help for the following modules:\n" "produce a help for the following modules:\n"
" write-perf\n" " write-perf\n"
" overwrite-perf\n" " overwrite-perf\n"
" read-perf\n" " read-perf\n"
" cache-read-perf\n" " cache-read-perf\n"
" cache-write-perf\n") " cache-write-perf\n")("module", po::value<std::string>(),
("module", po::value<std::string>(),
"Runs the given module"); "Runs the given module");
po::options_description benchmarkOptions("Benchmark options."); po::options_description benchmarkOptions("Benchmark options.");
benchmarkOptions.add_options() benchmarkOptions.add_options()("dataSetSize",
("dataSetSize", po::value<std::size_t>()->default_value(CommandLineOptions::c_defaultDataSetSize), "data set size") po::value<std::size_t>()->default_value(
("numBuckets", po::value<std::uint32_t>()->default_value(CommandLineOptions::c_defaultNumBuckets), "number of buckets") CommandLineOptions::c_defaultDataSetSize),
("keySize", po::value<std::uint16_t>()->default_value(CommandLineOptions::c_defaultKeySize), "key size in bytes") "data set size")(
("valueSize", po::value<std::uint32_t>()->default_value(CommandLineOptions::c_defaultValueSize), "value size in bytes") "numBuckets",
("randomizeValueSize", "randomize value size") po::value<std::uint32_t>()->default_value(
("numIterationsPerGetContext", po::value<std::uint32_t>()->default_value(CommandLineOptions::c_defaultNumIterationsPerGetContext), "number of iterations per GetContext()") CommandLineOptions::c_defaultNumBuckets),
("numThreads", po::value<std::uint16_t>()->default_value(CommandLineOptions::c_defaultNumThreads), "number of threads to create") "number of buckets")("keySize",
("epochProcessingInterval", po::value<std::uint32_t>()->default_value(CommandLineOptions::c_defaultEpochProcessingIntervalInMilli), "epoch processing interval (ms)") po::value<std::uint16_t>()->default_value(
("numActionsQueue", po::value<std::uint8_t>()->default_value(CommandLineOptions::c_defaultNumActionsQueue), "number of actions queue") CommandLineOptions::c_defaultKeySize),
("recordTimeToLive", po::value<std::uint32_t>()->default_value(CommandLineOptions::c_defaultRecordTimeToLiveInSeconds), "record time to live (s)") "key size in bytes")(
("cacheSize", po::value<std::uint64_t>()->default_value(CommandLineOptions::c_defaultCacheSizeInBytes), "cache size in bytes") "valueSize",
("forceTimeBasedEviction", po::value<bool>()->default_value(CommandLineOptions::c_defaultForceTimeBasedEviction), "force time based eviction"); po::value<std::uint32_t>()->default_value(
CommandLineOptions::c_defaultValueSize),
"value size in bytes")("randomizeValueSize", "randomize value size")(
"numIterationsPerGetContext",
po::value<std::uint32_t>()->default_value(
CommandLineOptions::c_defaultNumIterationsPerGetContext),
"number of iterations per GetContext()")(
"numThreads",
po::value<std::uint16_t>()->default_value(
CommandLineOptions::c_defaultNumThreads),
"number of threads to create")(
"epochProcessingInterval",
po::value<std::uint32_t>()->default_value(
CommandLineOptions::c_defaultEpochProcessingIntervalInMilli),
"epoch processing interval (ms)")(
"numActionsQueue",
po::value<std::uint8_t>()->default_value(
CommandLineOptions::c_defaultNumActionsQueue),
"number of actions queue")(
"recordTimeToLive",
po::value<std::uint32_t>()->default_value(
CommandLineOptions::c_defaultRecordTimeToLiveInSeconds),
"record time to live (s)")(
"cacheSize",
po::value<std::uint64_t>()->default_value(
CommandLineOptions::c_defaultCacheSizeInBytes),
"cache size in bytes")(
"forceTimeBasedEviction",
po::value<bool>()->default_value(
CommandLineOptions::c_defaultForceTimeBasedEviction),
"force time based eviction");
po::options_description all("Allowed options"); po::options_description all("Allowed options");
all.add(general).add(benchmarkOptions); all.add(general).add(benchmarkOptions);
@ -610,78 +584,62 @@ CommandLineOptions Parse(int argc, char** argv)
CommandLineOptions options; CommandLineOptions options;
if (vm.count("help")) if (vm.count("help")) {
{
std::cout << all; std::cout << all;
} } else if (vm.count("module")) {
else if (vm.count("module"))
{
options.m_module = vm["module"].as<std::string>(); options.m_module = vm["module"].as<std::string>();
if (vm.count("dataSetSize")) if (vm.count("dataSetSize")) {
{
options.m_dataSetSize = vm["dataSetSize"].as<std::size_t>(); options.m_dataSetSize = vm["dataSetSize"].as<std::size_t>();
} }
if (vm.count("numBuckets")) if (vm.count("numBuckets")) {
{
options.m_numBuckets = vm["numBuckets"].as<std::uint32_t>(); options.m_numBuckets = vm["numBuckets"].as<std::uint32_t>();
} }
if (vm.count("keySize")) if (vm.count("keySize")) {
{
options.m_keySize = vm["keySize"].as<std::uint16_t>(); options.m_keySize = vm["keySize"].as<std::uint16_t>();
} }
if (vm.count("valueSize")) if (vm.count("valueSize")) {
{
options.m_valueSize = vm["valueSize"].as<std::uint32_t>(); options.m_valueSize = vm["valueSize"].as<std::uint32_t>();
} }
if (vm.count("randomizeValueSize")) if (vm.count("randomizeValueSize")) {
{
options.m_randomizeValueSize = true; options.m_randomizeValueSize = true;
} }
if (vm.count("numIterationsPerGetContext")) if (vm.count("numIterationsPerGetContext")) {
{ options.m_numIterationsPerGetContext =
options.m_numIterationsPerGetContext = vm["numIterationsPerGetContext"].as<std::uint32_t>(); vm["numIterationsPerGetContext"].as<std::uint32_t>();
} }
if (vm.count("numThreads")) if (vm.count("numThreads")) {
{
options.m_numThreads = vm["numThreads"].as<std::uint16_t>(); options.m_numThreads = vm["numThreads"].as<std::uint16_t>();
} }
if (vm.count("epochProcessingInterval")) if (vm.count("epochProcessingInterval")) {
{ options.m_epochProcessingIntervalInMilli =
options.m_epochProcessingIntervalInMilli = vm["epochProcessingInterval"].as<std::uint32_t>(); vm["epochProcessingInterval"].as<std::uint32_t>();
} }
if (vm.count("numActionsQueue")) if (vm.count("numActionsQueue")) {
{
options.m_numActionsQueue = vm["numActionsQueue"].as<std::uint8_t>(); options.m_numActionsQueue = vm["numActionsQueue"].as<std::uint8_t>();
} }
if (vm.count("recordTimeToLive")) if (vm.count("recordTimeToLive")) {
{ options.m_recordTimeToLiveInSeconds =
options.m_recordTimeToLiveInSeconds = vm["recordTimeToLive"].as<std::uint32_t>(); vm["recordTimeToLive"].as<std::uint32_t>();
} }
if (vm.count("cacheSize")) if (vm.count("cacheSize")) {
{
options.m_cacheSizeInBytes = vm["cacheSize"].as<std::uint64_t>(); options.m_cacheSizeInBytes = vm["cacheSize"].as<std::uint64_t>();
} }
if (vm.count("forceTimeBasedEviction")) if (vm.count("forceTimeBasedEviction")) {
{ options.m_forceTimeBasedEviction =
options.m_forceTimeBasedEviction = vm["forceTimeBasedEviction"].as<bool>(); vm["forceTimeBasedEviction"].as<bool>();
} }
} } else {
else
{
std::cout << all; std::cout << all;
} }
return options; return options;
} }
int main(int argc, char** argv) {
int main(int argc, char** argv)
{
auto options = Parse(argc, argv); auto options = Parse(argc, argv);
if (options.m_module.empty()) if (options.m_module.empty()) {
{
return 0; return 0;
} }
@ -689,22 +647,16 @@ int main(int argc, char** argv)
PrintHardwareInfo(); PrintHardwareInfo();
if (options.m_module == "write-perf" if (options.m_module == "write-perf" ||
|| options.m_module == "overwrite-perf" options.m_module == "overwrite-perf" ||
|| options.m_module == "cache-write-perf") options.m_module == "cache-write-perf") {
{
WritePerfTest(options); WritePerfTest(options);
} } else if (options.m_module == "read-perf" ||
else if (options.m_module == "read-perf" options.m_module == "cache-read-perf") {
|| options.m_module == "cache-read-perf")
{
ReadPerfTest(options); ReadPerfTest(options);
} } else {
else
{
std::cout << "Unknown module: " << options.m_module << std::endl; std::cout << "Unknown module: " << options.m_module << std::endl;
} }
return 0; return 0;
} }

Просмотреть файл

@ -6,21 +6,16 @@
using namespace L4; using namespace L4;
void SimpleExample() void SimpleExample() {
{ EpochManagerConfig epochConfig{1000, std::chrono::milliseconds(100), 1};
EpochManagerConfig epochConfig{ 1000, std::chrono::milliseconds(100), 1 }; LocalMemory::HashTableService service{epochConfig};
LocalMemory::HashTableService service{ epochConfig };
auto hashTableIndex = service.AddHashTable( auto hashTableIndex = service.AddHashTable(
HashTableConfig("Table1", HashTableConfig::Setting{ 1000000 })); HashTableConfig("Table1", HashTableConfig::Setting{1000000}));
std::vector<std::pair<std::string, std::string>> keyValuePairs = std::vector<std::pair<std::string, std::string>> keyValuePairs = {
{ {"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"},
{ "key1", "value1" }, {"key4", "value4"}, {"key5", "value5"},
{ "key2", "value2" },
{ "key3", "value3" },
{ "key4", "value4" },
{ "key5", "value5" },
}; };
// Write data. // Write data.
@ -28,8 +23,7 @@ void SimpleExample()
auto context = service.GetContext(); auto context = service.GetContext();
auto& hashTable = context[hashTableIndex]; auto& hashTable = context[hashTableIndex];
for (const auto& keyValuePair : keyValuePairs) for (const auto& keyValuePair : keyValuePairs) {
{
const auto& keyStr = keyValuePair.first; const auto& keyStr = keyValuePair.first;
const auto& valStr = keyValuePair.second; const auto& valStr = keyValuePair.second;
@ -53,8 +47,7 @@ void SimpleExample()
// operator[] on the context and Get() are lock-free. // operator[] on the context and Get() are lock-free.
auto& hashTable = context[hashTableIndex]; auto& hashTable = context[hashTableIndex];
for (const auto& keyValuePair : keyValuePairs) for (const auto& keyValuePair : keyValuePairs) {
{
const auto& keyStr = keyValuePair.first; const auto& keyStr = keyValuePair.first;
IWritableHashTable::Key key; IWritableHashTable::Key key;
@ -64,13 +57,14 @@ void SimpleExample()
IWritableHashTable::Value val; IWritableHashTable::Value val;
hashTable.Get(key, val); hashTable.Get(key, val);
std::cout << std::string(reinterpret_cast<const char*>(val.m_data), val.m_size) << std::endl; std::cout << std::string(reinterpret_cast<const char*>(val.m_data),
val.m_size)
<< std::endl;
} }
} }
} }
void CacheHashTableExample() void CacheHashTableExample() {
{
LocalMemory::HashTableService service; LocalMemory::HashTableService service;
HashTableConfig::Cache cacheConfig{ HashTableConfig::Cache cacheConfig{
@ -79,22 +73,17 @@ void CacheHashTableExample()
true // Remove any expired records during eviction. true // Remove any expired records during eviction.
}; };
auto hashTableIndex = service.AddHashTable( auto hashTableIndex = service.AddHashTable(HashTableConfig(
HashTableConfig( "Table1", HashTableConfig::Setting{1000000}, cacheConfig));
"Table1",
HashTableConfig::Setting{ 1000000 },
cacheConfig));
(void)hashTableIndex; (void)hashTableIndex;
// Use hash table similar to SimpleExample(). // Use hash table similar to SimpleExample().
} }
int main() int main() {
{
SimpleExample(); SimpleExample();
CacheHashTableExample(); CacheHashTableExample();
return 0; return 0;
} }

Просмотреть файл

@ -1,109 +1,98 @@
#include <boost/test/unit_test.hpp>
#include <algorithm> #include <algorithm>
#include <boost/test/unit_test.hpp>
#include <sstream> #include <sstream>
#include "Utils.h"
#include "Mocks.h"
#include "CheckedAllocator.h" #include "CheckedAllocator.h"
#include "L4/HashTable/Common/Record.h"
#include "L4/HashTable/Cache/Metadata.h"
#include "L4/HashTable/Cache/HashTable.h" #include "L4/HashTable/Cache/HashTable.h"
#include "L4/HashTable/Cache/Metadata.h"
#include "L4/HashTable/Common/Record.h"
#include "Mocks.h"
#include "Utils.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
using namespace HashTable::Cache; using namespace HashTable::Cache;
using namespace std::chrono; using namespace std::chrono;
class MockClock class MockClock {
{ public:
public:
MockClock() = default; MockClock() = default;
seconds GetCurrentEpochTime() const seconds GetCurrentEpochTime() const { return s_currentEpochTime; }
{
return s_currentEpochTime;
}
static void SetEpochTime(seconds time) static void SetEpochTime(seconds time) { s_currentEpochTime = time; }
{
s_currentEpochTime = time;
}
static void IncrementEpochTime(seconds increment) static void IncrementEpochTime(seconds increment) {
{
s_currentEpochTime += increment; s_currentEpochTime += increment;
} }
private: private:
static seconds s_currentEpochTime; static seconds s_currentEpochTime;
}; };
seconds MockClock::s_currentEpochTime{ 0U }; seconds MockClock::s_currentEpochTime{0U};
class CacheHashTableTestFixture {
class CacheHashTableTestFixture public:
{
public:
using Allocator = CheckedAllocator<>; using Allocator = CheckedAllocator<>;
using CacheHashTable = WritableHashTable<Allocator, MockClock>; using CacheHashTable = WritableHashTable<Allocator, MockClock>;
using ReadOnlyCacheHashTable = ReadOnlyHashTable<Allocator, MockClock>; using ReadOnlyCacheHashTable = ReadOnlyHashTable<Allocator, MockClock>;
using HashTable = CacheHashTable::HashTable; using HashTable = CacheHashTable::HashTable;
CacheHashTableTestFixture() CacheHashTableTestFixture()
: m_allocator{} : m_allocator{},
, m_hashTable { HashTable::Setting{ 100U }, m_allocator } m_hashTable{HashTable::Setting{100U}, m_allocator},
, m_epochManager{} m_epochManager{} {
{ MockClock::SetEpochTime(seconds{0U});
MockClock::SetEpochTime(seconds{ 0U });
} }
CacheHashTableTestFixture(const CacheHashTableTestFixture&) = delete; CacheHashTableTestFixture(const CacheHashTableTestFixture&) = delete;
CacheHashTableTestFixture& operator=(const CacheHashTableTestFixture&) = delete; CacheHashTableTestFixture& operator=(const CacheHashTableTestFixture&) =
delete;
protected: protected:
template <typename TCacheHashTable> template <typename TCacheHashTable>
bool Get(TCacheHashTable& hashTable, const std::string& key, IReadOnlyHashTable::Value& value) bool Get(TCacheHashTable& hashTable,
{ const std::string& key,
IReadOnlyHashTable::Value& value) {
return hashTable.Get( return hashTable.Get(
Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()), Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()), value);
value);
} }
void Add(CacheHashTable& hashTable, const std::string& key, const std::string& value) void Add(CacheHashTable& hashTable,
{ const std::string& key,
const std::string& value) {
hashTable.Add( hashTable.Add(
Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()), Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()),
Utils::ConvertFromString<IReadOnlyHashTable::Value>(value.c_str())); Utils::ConvertFromString<IReadOnlyHashTable::Value>(value.c_str()));
} }
void Remove(CacheHashTable& hashTable, const std::string& key) void Remove(CacheHashTable& hashTable, const std::string& key) {
{ hashTable.Remove(
hashTable.Remove(Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str())); Utils::ConvertFromString<IReadOnlyHashTable::Key>(key.c_str()));
} }
template <typename TCacheHashTable> template <typename TCacheHashTable>
bool CheckRecord(TCacheHashTable& hashTable, const std::string& key, const std::string& expectedValue) bool CheckRecord(TCacheHashTable& hashTable,
{ const std::string& key,
const std::string& expectedValue) {
IReadOnlyHashTable::Value value; IReadOnlyHashTable::Value value;
return Get(hashTable, key, value) && AreTheSame(value, expectedValue); return Get(hashTable, key, value) && AreTheSame(value, expectedValue);
} }
bool AreTheSame(const IReadOnlyHashTable::Value& actual, const std::string& expected) bool AreTheSame(const IReadOnlyHashTable::Value& actual,
{ const std::string& expected) {
return (actual.m_size == expected.size()) return (actual.m_size == expected.size()) &&
&& !memcmp(actual.m_data, expected.c_str(), actual.m_size); !memcmp(actual.m_data, expected.c_str(), actual.m_size);
} }
template <typename Blob> template <typename Blob>
bool Exist(const Blob& actual, const std::vector<std::string>& expectedSet) bool Exist(const Blob& actual, const std::vector<std::string>& expectedSet) {
{ const std::string actualStr(reinterpret_cast<const char*>(actual.m_data),
const std::string actualStr(
reinterpret_cast<const char*>(actual.m_data),
actual.m_size); actual.m_size);
return std::find(expectedSet.cbegin(), expectedSet.cend(), actualStr) != expectedSet.cend(); return std::find(expectedSet.cbegin(), expectedSet.cend(), actualStr) !=
expectedSet.cend();
} }
Allocator m_allocator; Allocator m_allocator;
@ -112,31 +101,28 @@ protected:
MockClock m_clock; MockClock m_clock;
}; };
BOOST_AUTO_TEST_SUITE(CacheHashTableTests) BOOST_AUTO_TEST_SUITE(CacheHashTableTests)
BOOST_AUTO_TEST_CASE(MetadataTest) {
BOOST_AUTO_TEST_CASE(MetadataTest)
{
std::vector<std::uint8_t> buffer(20); std::vector<std::uint8_t> buffer(20);
// The following will test with 1..8 byte alignments. // The following will test with 1..8 byte alignments.
for (std::uint16_t i = 0U; i < 8U; ++i) for (std::uint16_t i = 0U; i < 8U; ++i) {
{ std::uint32_t* metadataBuffer =
std::uint32_t* metadataBuffer = reinterpret_cast<std::uint32_t*>(buffer.data() + i); reinterpret_cast<std::uint32_t*>(buffer.data() + i);
seconds currentEpochTime{ 0x7FABCDEF }; seconds currentEpochTime{0x7FABCDEF};
Metadata metadata{ metadataBuffer, currentEpochTime }; Metadata metadata{metadataBuffer, currentEpochTime};
BOOST_CHECK(currentEpochTime == metadata.GetEpochTime()); BOOST_CHECK(currentEpochTime == metadata.GetEpochTime());
// 10 seconds have elapsed. // 10 seconds have elapsed.
currentEpochTime += seconds{ 10U }; currentEpochTime += seconds{10U};
// Check the expiration based on the time to live value. // Check the expiration based on the time to live value.
BOOST_CHECK(!metadata.IsExpired(currentEpochTime, seconds{ 15 })); BOOST_CHECK(!metadata.IsExpired(currentEpochTime, seconds{15}));
BOOST_CHECK(!metadata.IsExpired(currentEpochTime, seconds{ 10 })); BOOST_CHECK(!metadata.IsExpired(currentEpochTime, seconds{10}));
BOOST_CHECK(metadata.IsExpired(currentEpochTime, seconds{ 5U })); BOOST_CHECK(metadata.IsExpired(currentEpochTime, seconds{5U}));
// Test access state. // Test access state.
BOOST_CHECK(!metadata.IsAccessed()); BOOST_CHECK(!metadata.IsAccessed());
@ -149,46 +135,33 @@ BOOST_AUTO_TEST_CASE(MetadataTest)
} }
} }
BOOST_FIXTURE_TEST_CASE(ExpirationTest, CacheHashTableTestFixture) {
BOOST_FIXTURE_TEST_CASE(ExpirationTest, CacheHashTableTestFixture)
{
// Don't care about evict in this test case, so make the cache size big. // Don't care about evict in this test case, so make the cache size big.
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF; constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
constexpr seconds c_recordTimeToLive{ 20U }; constexpr seconds c_recordTimeToLive{20U};
CacheHashTable hashTable( CacheHashTable hashTable(m_hashTable, m_epochManager, c_maxCacheSizeInBytes,
m_hashTable, c_recordTimeToLive, false);
m_epochManager,
c_maxCacheSizeInBytes,
c_recordTimeToLive,
false);
const std::vector<std::pair<std::string, std::string>> c_keyValuePairs = const std::vector<std::pair<std::string, std::string>> c_keyValuePairs = {
{ {"key1", "value1"},
{ "key1", "value1" }, {"key2", "value2"},
{ "key2", "value2" }, {"key3", "value3"},
{ "key3", "value3" }, {"key4", "value4"},
{ "key4", "value4" }, {"key5", "value5"}};
{ "key5", "value5" }
};
// Add 5 records at a different epoch time (10 seconds increment). // Add 5 records at a different epoch time (10 seconds increment).
for (const auto& pair : c_keyValuePairs) for (const auto& pair : c_keyValuePairs) {
{ MockClock::IncrementEpochTime(seconds{10});
MockClock::IncrementEpochTime(seconds{ 10 });
Add(hashTable, pair.first, pair.second); Add(hashTable, pair.first, pair.second);
// Make sure the records can be retrieved right away. The record has not been // Make sure the records can be retrieved right away. The record has not
// expired since the clock hasn't moved yet. // been expired since the clock hasn't moved yet.
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second)); BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
} }
const auto& perfData = hashTable.GetPerfData(); const auto& perfData = hashTable.GetPerfData();
Utils::ValidateCounters( Utils::ValidateCounters(perfData, {{HashTablePerfCounter::CacheHitCount, 5}});
perfData,
{
{ HashTablePerfCounter::CacheHitCount, 5 }
});
// Now we have the following data sets: // Now we have the following data sets:
// | Key | Value | Creation time | // | Key | Value | Creation time |
@ -200,65 +173,49 @@ BOOST_FIXTURE_TEST_CASE(ExpirationTest, CacheHashTableTestFixture)
// And the current clock is at 50. // And the current clock is at 50.
// Do look ups and check expired records. // Do look ups and check expired records.
for (const auto& pair : c_keyValuePairs) for (const auto& pair : c_keyValuePairs) {
{
IReadOnlyHashTable::Value value; IReadOnlyHashTable::Value value;
// Our time to live value is 20, so key0 and key0 records should be expired. // Our time to live value is 20, so key0 and key0 records should be expired.
if (pair.first == "key1" || pair.first == "key2") if (pair.first == "key1" || pair.first == "key2") {
{
BOOST_CHECK(!Get(hashTable, pair.first, value)); BOOST_CHECK(!Get(hashTable, pair.first, value));
} } else {
else
{
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second)); BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
} }
} }
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData, {{HashTablePerfCounter::CacheHitCount, 8},
{ {HashTablePerfCounter::CacheMissCount, 2}});
{ HashTablePerfCounter::CacheHitCount, 8 },
{ HashTablePerfCounter::CacheMissCount, 2 }
});
MockClock::IncrementEpochTime(seconds{ 100 }); MockClock::IncrementEpochTime(seconds{100});
// All the records should be expired now. // All the records should be expired now.
for (const auto& pair : c_keyValuePairs) for (const auto& pair : c_keyValuePairs) {
{
IReadOnlyHashTable::Value value; IReadOnlyHashTable::Value value;
BOOST_CHECK(!Get(hashTable, pair.first, value)); BOOST_CHECK(!Get(hashTable, pair.first, value));
} }
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData, {{HashTablePerfCounter::CacheHitCount, 8},
{ {HashTablePerfCounter::CacheMissCount, 7}});
{ HashTablePerfCounter::CacheHitCount, 8 },
{ HashTablePerfCounter::CacheMissCount, 7 }
});
} }
BOOST_FIXTURE_TEST_CASE(CacheHashTableIteratorTest, CacheHashTableTestFixture) {
BOOST_FIXTURE_TEST_CASE(CacheHashTableIteratorTest, CacheHashTableTestFixture)
{
// Don't care about evict in this test case, so make the cache size big. // Don't care about evict in this test case, so make the cache size big.
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF; constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
constexpr seconds c_recordTimeToLive{ 20U }; constexpr seconds c_recordTimeToLive{20U};
CacheHashTable hashTable( CacheHashTable hashTable(m_hashTable, m_epochManager, c_maxCacheSizeInBytes,
m_hashTable, c_recordTimeToLive, false);
m_epochManager,
c_maxCacheSizeInBytes,
c_recordTimeToLive,
false);
const std::vector<std::string> c_keys = { "key1", "key2", "key3", "key4", "key5" }; const std::vector<std::string> c_keys = {"key1", "key2", "key3", "key4",
const std::vector<std::string> c_vals = { "val1", "val2", "val3", "val4", "val5" }; "key5"};
const std::vector<std::string> c_vals = {"val1", "val2", "val3", "val4",
"val5"};
// Add 5 records at a different epoch time (3 seconds increment). // Add 5 records at a different epoch time (3 seconds increment).
for (std::size_t i = 0; i < c_keys.size(); ++i) for (std::size_t i = 0; i < c_keys.size(); ++i) {
{ MockClock::IncrementEpochTime(seconds{3});
MockClock::IncrementEpochTime(seconds{ 3 });
Add(hashTable, c_keys[i], c_vals[i]); Add(hashTable, c_keys[i], c_vals[i]);
} }
@ -273,8 +230,7 @@ BOOST_FIXTURE_TEST_CASE(CacheHashTableIteratorTest, CacheHashTableTestFixture)
auto iterator = hashTable.GetIterator(); auto iterator = hashTable.GetIterator();
std::uint16_t numRecords = 0; std::uint16_t numRecords = 0;
while (iterator->MoveNext()) while (iterator->MoveNext()) {
{
++numRecords; ++numRecords;
BOOST_CHECK(Exist(iterator->GetKey(), c_keys)); BOOST_CHECK(Exist(iterator->GetKey(), c_keys));
BOOST_CHECK(Exist(iterator->GetValue(), c_vals)); BOOST_CHECK(Exist(iterator->GetValue(), c_vals));
@ -283,169 +239,146 @@ BOOST_FIXTURE_TEST_CASE(CacheHashTableIteratorTest, CacheHashTableTestFixture)
BOOST_CHECK_EQUAL(numRecords, 5); BOOST_CHECK_EQUAL(numRecords, 5);
// The clock becomes 30 and key1, key2 and key3 should expire. // The clock becomes 30 and key1, key2 and key3 should expire.
MockClock::IncrementEpochTime(seconds{ 15 }); MockClock::IncrementEpochTime(seconds{15});
iterator = hashTable.GetIterator(); iterator = hashTable.GetIterator();
numRecords = 0; numRecords = 0;
while (iterator->MoveNext()) while (iterator->MoveNext()) {
{
++numRecords; ++numRecords;
BOOST_CHECK( BOOST_CHECK(
Exist( Exist(iterator->GetKey(),
iterator->GetKey(), std::vector<std::string>{c_keys.cbegin() + 2, c_keys.cend()}));
std::vector<std::string>{ c_keys.cbegin() + 2, c_keys.cend() }));
BOOST_CHECK( BOOST_CHECK(
Exist( Exist(iterator->GetValue(),
iterator->GetValue(), std::vector<std::string>{c_vals.cbegin() + 2, c_vals.cend()}));
std::vector<std::string>{ c_vals.cbegin() + 2, c_vals.cend() }));
} }
BOOST_CHECK_EQUAL(numRecords, 2); BOOST_CHECK_EQUAL(numRecords, 2);
// The clock becomes 40 and all records should be expired now. // The clock becomes 40 and all records should be expired now.
MockClock::IncrementEpochTime(seconds{ 10 }); MockClock::IncrementEpochTime(seconds{10});
iterator = hashTable.GetIterator(); iterator = hashTable.GetIterator();
while (iterator->MoveNext()) while (iterator->MoveNext()) {
{
BOOST_CHECK(false); BOOST_CHECK(false);
} }
} }
BOOST_FIXTURE_TEST_CASE(TimeBasedEvictionTest, CacheHashTableTestFixture) {
BOOST_FIXTURE_TEST_CASE(TimeBasedEvictionTest, CacheHashTableTestFixture) // We only care about time-based eviction in this test, so make the cache size
{ // big.
// We only care about time-based eviction in this test, so make the cache size big.
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF; constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
constexpr seconds c_recordTimeToLive{ 10U }; constexpr seconds c_recordTimeToLive{10U};
// Hash table with one bucket makes testing the time-based eviction easy. // Hash table with one bucket makes testing the time-based eviction easy.
HashTable internalHashTable{ HashTable::Setting{ 1 }, m_allocator }; HashTable internalHashTable{HashTable::Setting{1}, m_allocator};
CacheHashTable hashTable( CacheHashTable hashTable(internalHashTable, m_epochManager,
internalHashTable, c_maxCacheSizeInBytes, c_recordTimeToLive, true);
m_epochManager,
c_maxCacheSizeInBytes,
c_recordTimeToLive,
true);
const std::vector<std::pair<std::string, std::string>> c_keyValuePairs = const std::vector<std::pair<std::string, std::string>> c_keyValuePairs = {
{ {"key1", "value1"},
{ "key1", "value1" }, {"key2", "value2"},
{ "key2", "value2" }, {"key3", "value3"},
{ "key3", "value3" }, {"key4", "value4"},
{ "key4", "value4" }, {"key5", "value5"}};
{ "key5", "value5" }
};
for (const auto& pair : c_keyValuePairs) for (const auto& pair : c_keyValuePairs) {
{
Add(hashTable, pair.first, pair.second); Add(hashTable, pair.first, pair.second);
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second)); BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
} }
const auto& perfData = hashTable.GetPerfData(); const auto& perfData = hashTable.GetPerfData();
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData,
{ {
{ HashTablePerfCounter::CacheHitCount, 5 }, {HashTablePerfCounter::CacheHitCount, 5},
{ HashTablePerfCounter::RecordsCount, 5 }, {HashTablePerfCounter::RecordsCount, 5},
{ HashTablePerfCounter::EvictedRecordsCount, 0 }, {HashTablePerfCounter::EvictedRecordsCount, 0},
}); });
MockClock::IncrementEpochTime(seconds{ 20 }); MockClock::IncrementEpochTime(seconds{20});
// All the records should be expired now. // All the records should be expired now.
for (const auto& pair : c_keyValuePairs) for (const auto& pair : c_keyValuePairs) {
{
IReadOnlyHashTable::Value value; IReadOnlyHashTable::Value value;
BOOST_CHECK(!Get(hashTable, pair.first, value)); BOOST_CHECK(!Get(hashTable, pair.first, value));
} }
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData,
{ {
{ HashTablePerfCounter::CacheHitCount, 5 }, {HashTablePerfCounter::CacheHitCount, 5},
{ HashTablePerfCounter::CacheMissCount, 5 }, {HashTablePerfCounter::CacheMissCount, 5},
{ HashTablePerfCounter::RecordsCount, 5 }, {HashTablePerfCounter::RecordsCount, 5},
{ HashTablePerfCounter::EvictedRecordsCount, 0 }, {HashTablePerfCounter::EvictedRecordsCount, 0},
}); });
// Now try to add one record and all the expired records should be evicted. // Now try to add one record and all the expired records should be evicted.
const auto& keyValuePair = c_keyValuePairs[0]; const auto& keyValuePair = c_keyValuePairs[0];
Add(hashTable, keyValuePair.first, keyValuePair.second); Add(hashTable, keyValuePair.first, keyValuePair.second);
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData,
{ {
{ HashTablePerfCounter::RecordsCount, 1 }, {HashTablePerfCounter::RecordsCount, 1},
{ HashTablePerfCounter::EvictedRecordsCount, 5 }, {HashTablePerfCounter::EvictedRecordsCount, 5},
}); });
} }
BOOST_FIXTURE_TEST_CASE(EvcitAllRecordsTest, CacheHashTableTestFixture) {
BOOST_FIXTURE_TEST_CASE(EvcitAllRecordsTest, CacheHashTableTestFixture)
{
const auto& perfData = m_hashTable.m_perfData; const auto& perfData = m_hashTable.m_perfData;
const auto initialTotalIndexSize = perfData.Get(HashTablePerfCounter::TotalIndexSize); const auto initialTotalIndexSize =
perfData.Get(HashTablePerfCounter::TotalIndexSize);
const std::uint64_t c_maxCacheSizeInBytes = 500 + initialTotalIndexSize; const std::uint64_t c_maxCacheSizeInBytes = 500 + initialTotalIndexSize;
constexpr seconds c_recordTimeToLive{ 5 }; constexpr seconds c_recordTimeToLive{5};
CacheHashTable hashTable{ CacheHashTable hashTable{m_hashTable, m_epochManager, c_maxCacheSizeInBytes,
m_hashTable, c_recordTimeToLive, false};
m_epochManager,
c_maxCacheSizeInBytes,
c_recordTimeToLive,
false };
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData,
{ {
{ HashTablePerfCounter::EvictedRecordsCount, 0 }, {HashTablePerfCounter::EvictedRecordsCount, 0},
}); });
const std::vector<std::pair<std::string, std::string>> c_keyValuePairs = const std::vector<std::pair<std::string, std::string>> c_keyValuePairs = {
{ {"key1", "value1"},
{ "key1", "value1" }, {"key2", "value2"},
{ "key2", "value2" }, {"key3", "value3"},
{ "key3", "value3" }, {"key4", "value4"},
{ "key4", "value4" }, {"key5", "value5"}};
{ "key5", "value5" }
};
for (const auto& pair : c_keyValuePairs) for (const auto& pair : c_keyValuePairs) {
{
Add(hashTable, pair.first, pair.second); Add(hashTable, pair.first, pair.second);
} }
using L4::HashTable::RecordSerializer; using L4::HashTable::RecordSerializer;
// Variable key/value sizes. // Variable key/value sizes.
const auto recordOverhead = RecordSerializer{ 0U, 0U }.CalculateRecordOverhead(); const auto recordOverhead =
RecordSerializer{0U, 0U}.CalculateRecordOverhead();
Utils::ValidateCounters( Utils::ValidateCounters(
perfData, perfData,
{ {
{ HashTablePerfCounter::RecordsCount, c_keyValuePairs.size() }, {HashTablePerfCounter::RecordsCount, c_keyValuePairs.size()},
{ HashTablePerfCounter::TotalIndexSize, initialTotalIndexSize + (c_keyValuePairs.size() * recordOverhead) }, {HashTablePerfCounter::TotalIndexSize,
{ HashTablePerfCounter::EvictedRecordsCount, 0 }, initialTotalIndexSize + (c_keyValuePairs.size() * recordOverhead)},
{HashTablePerfCounter::EvictedRecordsCount, 0},
}); });
// Make sure all data records added are present and update the access status for each // Make sure all data records added are present and update the access status
// record in order to test that accessed records are deleted when it's under memory constraint. // for each record in order to test that accessed records are deleted when
for (const auto& pair : c_keyValuePairs) // it's under memory constraint.
{ for (const auto& pair : c_keyValuePairs) {
BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second)); BOOST_CHECK(CheckRecord(hashTable, pair.first, pair.second));
} }
// Now insert a record that will force all the records to be evicted due to size. // Now insert a record that will force all the records to be evicted due to
// size.
std::string bigRecordKeyStr(10, 'k'); std::string bigRecordKeyStr(10, 'k');
std::string bigRecordValStr(500, 'v'); std::string bigRecordValStr(500, 'v');
Add(hashTable, bigRecordKeyStr, bigRecordValStr); Add(hashTable, bigRecordKeyStr, bigRecordValStr);
// Make sure all the previously inserted records are evicted. // Make sure all the previously inserted records are evicted.
for (const auto& pair : c_keyValuePairs) for (const auto& pair : c_keyValuePairs) {
{
IReadOnlyHashTable::Value value; IReadOnlyHashTable::Value value;
BOOST_CHECK(!Get(hashTable, pair.first, value)); BOOST_CHECK(!Get(hashTable, pair.first, value));
} }
@ -456,37 +389,32 @@ BOOST_FIXTURE_TEST_CASE(EvcitAllRecordsTest, CacheHashTableTestFixture)
Utils::ValidateCounters( Utils::ValidateCounters(
perfData, perfData,
{ {
{ HashTablePerfCounter::RecordsCount, 1 }, {HashTablePerfCounter::RecordsCount, 1},
{ HashTablePerfCounter::TotalIndexSize, initialTotalIndexSize + (1 * recordOverhead) }, {HashTablePerfCounter::TotalIndexSize,
{ HashTablePerfCounter::EvictedRecordsCount, c_keyValuePairs.size() }, initialTotalIndexSize + (1 * recordOverhead)},
{HashTablePerfCounter::EvictedRecordsCount, c_keyValuePairs.size()},
}); });
} }
BOOST_FIXTURE_TEST_CASE(EvcitRecordsBasedOnAccessStatusTest,
CacheHashTableTestFixture) {
const std::uint64_t c_maxCacheSizeInBytes =
2000 + m_hashTable.m_perfData.Get(HashTablePerfCounter::TotalIndexSize);
const seconds c_recordTimeToLive{5};
BOOST_FIXTURE_TEST_CASE(EvcitRecordsBasedOnAccessStatusTest, CacheHashTableTestFixture) CacheHashTable hashTable(m_hashTable, m_epochManager, c_maxCacheSizeInBytes,
{ c_recordTimeToLive, false);
const std::uint64_t c_maxCacheSizeInBytes
= 2000 + m_hashTable.m_perfData.Get(HashTablePerfCounter::TotalIndexSize);
const seconds c_recordTimeToLive{ 5 };
CacheHashTable hashTable(
m_hashTable,
m_epochManager,
c_maxCacheSizeInBytes,
c_recordTimeToLive,
false);
constexpr std::uint32_t c_valueSize = 100; constexpr std::uint32_t c_valueSize = 100;
const std::string c_valStr(c_valueSize, 'v'); const std::string c_valStr(c_valueSize, 'v');
const auto& perfData = hashTable.GetPerfData(); const auto& perfData = hashTable.GetPerfData();
std::uint16_t key = 1; std::uint16_t key = 1;
while ((static_cast<std::uint64_t>(perfData.Get(HashTablePerfCounter::TotalIndexSize)) while ((static_cast<std::uint64_t>(
+ perfData.Get(HashTablePerfCounter::TotalKeySize) perfData.Get(HashTablePerfCounter::TotalIndexSize)) +
+ perfData.Get(HashTablePerfCounter::TotalValueSize) perfData.Get(HashTablePerfCounter::TotalKeySize) +
+ c_valueSize) perfData.Get(HashTablePerfCounter::TotalValueSize) + c_valueSize) <
< c_maxCacheSizeInBytes) c_maxCacheSizeInBytes) {
{
std::stringstream ss; std::stringstream ss;
ss << "key" << key; ss << "key" << key;
Add(hashTable, ss.str(), c_valStr); Add(hashTable, ss.str(), c_valStr);
@ -499,7 +427,8 @@ BOOST_FIXTURE_TEST_CASE(EvcitRecordsBasedOnAccessStatusTest, CacheHashTableTestF
// Look up with the "key1" key to update the access state. // Look up with the "key1" key to update the access state.
BOOST_CHECK(CheckRecord(hashTable, "key1", c_valStr)); BOOST_CHECK(CheckRecord(hashTable, "key1", c_valStr));
// Now add a new key, which triggers an eviction, but deletes other records than the "key1" record. // Now add a new key, which triggers an eviction, but deletes other records
// than the "key1" record.
Add(hashTable, "newkey", c_valStr); Add(hashTable, "newkey", c_valStr);
// Now, eviction should have happened. // Now, eviction should have happened.
@ -512,96 +441,77 @@ BOOST_FIXTURE_TEST_CASE(EvcitRecordsBasedOnAccessStatusTest, CacheHashTableTestF
BOOST_CHECK(CheckRecord(hashTable, "newkey", c_valStr)); BOOST_CHECK(CheckRecord(hashTable, "newkey", c_valStr));
} }
// This is similar to the one in ReadWriteHashTableTest, but necessary since
// This is similar to the one in ReadWriteHashTableTest, but necessary since cache store adds the meta values. // cache store adds the meta values.
BOOST_FIXTURE_TEST_CASE(FixedKeyValueHashTableTest, CacheHashTableTestFixture) BOOST_FIXTURE_TEST_CASE(FixedKeyValueHashTableTest, CacheHashTableTestFixture) {
{
// Fixed 4 byte keys and 6 byte values. // Fixed 4 byte keys and 6 byte values.
std::vector<HashTable::Setting> settings = std::vector<HashTable::Setting> settings = {
{ HashTable::Setting{100, 200, 4, 0}, HashTable::Setting{100, 200, 0, 6},
HashTable::Setting{ 100, 200, 4, 0 }, HashTable::Setting{100, 200, 4, 6}};
HashTable::Setting{ 100, 200, 0, 6 },
HashTable::Setting{ 100, 200, 4, 6 }
};
for (const auto& setting : settings) for (const auto& setting : settings) {
{
// Don't care about evict in this test case, so make the cache size big. // Don't care about evict in this test case, so make the cache size big.
constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF; constexpr std::uint64_t c_maxCacheSizeInBytes = 0xFFFFFFFF;
constexpr seconds c_recordTimeToLive{ 20U }; constexpr seconds c_recordTimeToLive{20U};
HashTable hashTable{ setting, m_allocator }; HashTable hashTable{setting, m_allocator};
CacheHashTable writableHashTable{ CacheHashTable writableHashTable{hashTable, m_epochManager,
hashTable, c_maxCacheSizeInBytes, c_recordTimeToLive,
m_epochManager, false};
c_maxCacheSizeInBytes,
c_recordTimeToLive,
false };
ReadOnlyCacheHashTable readOnlyHashTable{ hashTable, c_recordTimeToLive }; ReadOnlyCacheHashTable readOnlyHashTable{hashTable, c_recordTimeToLive};
constexpr std::uint8_t c_numRecords = 10; constexpr std::uint8_t c_numRecords = 10;
// Add records. // Add records.
for (std::uint8_t i = 0; i < c_numRecords; ++i) for (std::uint8_t i = 0; i < c_numRecords; ++i) {
{ Add(writableHashTable, "key" + std::to_string(i),
Add(writableHashTable, "key" + std::to_string(i), "value" + std::to_string(i)); "value" + std::to_string(i));
} }
Utils::ValidateCounters( Utils::ValidateCounters(writableHashTable.GetPerfData(),
writableHashTable.GetPerfData(), {{HashTablePerfCounter::RecordsCount, 10},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 10 }, {HashTablePerfCounter::TotalKeySize, 40},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalValueSize, 100},
{ HashTablePerfCounter::TotalKeySize, 40 }, {HashTablePerfCounter::MinKeySize, 4},
{ HashTablePerfCounter::TotalValueSize, 100 }, {HashTablePerfCounter::MaxKeySize, 4},
{ HashTablePerfCounter::MinKeySize, 4 }, {HashTablePerfCounter::MinValueSize, 10},
{ HashTablePerfCounter::MaxKeySize, 4 }, {HashTablePerfCounter::MaxValueSize, 10}});
{ HashTablePerfCounter::MinValueSize, 10 },
{ HashTablePerfCounter::MaxValueSize, 10 }
});
// Validate all the records added. // Validate all the records added.
for (std::uint8_t i = 0; i < c_numRecords; ++i) for (std::uint8_t i = 0; i < c_numRecords; ++i) {
{ CheckRecord(readOnlyHashTable, "key" + std::to_string(i),
CheckRecord(readOnlyHashTable, "key" + std::to_string(i), "value" + std::to_string(i)); "value" + std::to_string(i));
} }
// Remove first half of the records. // Remove first half of the records.
for (std::uint8_t i = 0; i < c_numRecords / 2; ++i) for (std::uint8_t i = 0; i < c_numRecords / 2; ++i) {
{
Remove(writableHashTable, "key" + std::to_string(i)); Remove(writableHashTable, "key" + std::to_string(i));
} }
Utils::ValidateCounters( Utils::ValidateCounters(writableHashTable.GetPerfData(),
writableHashTable.GetPerfData(), {{HashTablePerfCounter::RecordsCount, 5},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 5 }, {HashTablePerfCounter::TotalKeySize, 20},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalValueSize, 50}});
{ HashTablePerfCounter::TotalKeySize, 20 },
{ HashTablePerfCounter::TotalValueSize, 50 }
});
// Verify the records. // Verify the records.
for (std::uint8_t i = 0; i < c_numRecords; ++i) for (std::uint8_t i = 0; i < c_numRecords; ++i) {
{ if (i < (c_numRecords / 2)) {
if (i < (c_numRecords / 2))
{
IReadOnlyHashTable::Value value; IReadOnlyHashTable::Value value;
BOOST_CHECK(!Get(readOnlyHashTable, "key" + std::to_string(i), value)); BOOST_CHECK(!Get(readOnlyHashTable, "key" + std::to_string(i), value));
} } else {
else CheckRecord(readOnlyHashTable, "key" + std::to_string(i),
{ "value" + std::to_string(i));
CheckRecord(readOnlyHashTable, "key" + std::to_string(i), "value" + std::to_string(i));
} }
} }
// Expire all the records. // Expire all the records.
MockClock::IncrementEpochTime(seconds{ 100 }); MockClock::IncrementEpochTime(seconds{100});
// Verify the records. // Verify the records.
for (std::uint8_t i = 0; i < c_numRecords; ++i) for (std::uint8_t i = 0; i < c_numRecords; ++i) {
{
IReadOnlyHashTable::Value value; IReadOnlyHashTable::Value value;
BOOST_CHECK(!Get(readOnlyHashTable, "key" + std::to_string(i), value)); BOOST_CHECK(!Get(readOnlyHashTable, "key" + std::to_string(i), value));
} }

Просмотреть файл

@ -1,62 +1,50 @@
#pragma once #pragma once
#include <boost/test/unit_test.hpp>
#include <memory> #include <memory>
#include <set> #include <set>
#include <boost/test/unit_test.hpp>
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
struct AllocationAddressHolder : public std::set<void*> struct AllocationAddressHolder : public std::set<void*> {
{ ~AllocationAddressHolder() { BOOST_REQUIRE(empty()); }
~AllocationAddressHolder()
{
BOOST_REQUIRE(empty());
}
}; };
template <typename T = void> template <typename T = void>
class CheckedAllocator : public std::allocator<T> class CheckedAllocator : public std::allocator<T> {
{ public:
public:
using Base = std::allocator<T>; using Base = std::allocator<T>;
using pointer = typename Base::pointer; using pointer = typename Base::pointer;
template<class U> template <class U>
struct rebind struct rebind {
{
typedef CheckedAllocator<U> other; typedef CheckedAllocator<U> other;
}; };
CheckedAllocator() CheckedAllocator()
: m_allocationAddresses{ std::make_shared<AllocationAddressHolder>() } : m_allocationAddresses{std::make_shared<AllocationAddressHolder>()} {}
{}
CheckedAllocator(const CheckedAllocator<T>&) = default; CheckedAllocator(const CheckedAllocator<T>&) = default;
template<class U> template <class U>
CheckedAllocator(const CheckedAllocator<U>& other) CheckedAllocator(const CheckedAllocator<U>& other)
: m_allocationAddresses{ other.m_allocationAddresses } : m_allocationAddresses{other.m_allocationAddresses} {}
{}
template<class U> template <class U>
CheckedAllocator<T>& operator=(const CheckedAllocator<U>& other) CheckedAllocator<T>& operator=(const CheckedAllocator<U>& other) {
{
m_allocationAddresses = other.m_allocationAddresses; m_allocationAddresses = other.m_allocationAddresses;
return (*this); return (*this);
} }
pointer allocate(std::size_t count, std::allocator<void>::const_pointer hint = 0) pointer allocate(std::size_t count,
{ std::allocator<void>::const_pointer hint = 0) {
auto address = Base::allocate(count, hint); auto address = Base::allocate(count, hint);
BOOST_REQUIRE(m_allocationAddresses->insert(address).second); BOOST_REQUIRE(m_allocationAddresses->insert(address).second);
return address; return address;
} }
void deallocate(pointer ptr, std::size_t count) void deallocate(pointer ptr, std::size_t count) {
{
BOOST_REQUIRE(m_allocationAddresses->erase(ptr) == 1); BOOST_REQUIRE(m_allocationAddresses->erase(ptr) == 1);
Base::deallocate(ptr, count); Base::deallocate(ptr, count);
} }

Просмотреть файл

@ -1,47 +1,48 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <mutex>
#include <condition_variable> #include <condition_variable>
#include "Utils.h" #include <mutex>
#include "L4/Interprocess/Connection/ConnectionMonitor.h" #include "L4/Interprocess/Connection/ConnectionMonitor.h"
#include "L4/Interprocess/Connection/EndPointInfoUtils.h" #include "L4/Interprocess/Connection/EndPointInfoUtils.h"
#include "Utils.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
BOOST_AUTO_TEST_SUITE(ConnectionMonitorTests) BOOST_AUTO_TEST_SUITE(ConnectionMonitorTests)
BOOST_AUTO_TEST_CASE(ConnectionMonitorTest) BOOST_AUTO_TEST_CASE(ConnectionMonitorTest) {
{
std::vector<Interprocess::Connection::EndPointInfo> endPointsDisconnected; std::vector<Interprocess::Connection::EndPointInfo> endPointsDisconnected;
std::mutex lock; std::mutex lock;
std::condition_variable cv; std::condition_variable cv;
auto server = std::make_shared<Interprocess::Connection::ConnectionMonitor>(); auto server = std::make_shared<Interprocess::Connection::ConnectionMonitor>();
auto noOpCallback = [](const auto&) { throw std::runtime_error("This will not be called."); }; auto noOpCallback = [](const auto&) {
auto callback = [&](const auto& endPoint) throw std::runtime_error("This will not be called.");
{ };
std::unique_lock<std::mutex> guard{ lock }; auto callback = [&](const auto& endPoint) {
std::unique_lock<std::mutex> guard{lock};
endPointsDisconnected.emplace_back(endPoint); endPointsDisconnected.emplace_back(endPoint);
cv.notify_one(); cv.notify_one();
}; };
auto client1 = std::make_shared<Interprocess::Connection::ConnectionMonitor>(); auto client1 =
std::make_shared<Interprocess::Connection::ConnectionMonitor>();
client1->Register(server->GetLocalEndPointInfo(), noOpCallback); client1->Register(server->GetLocalEndPointInfo(), noOpCallback);
server->Register(client1->GetLocalEndPointInfo(), callback); server->Register(client1->GetLocalEndPointInfo(), callback);
// Registering the same end point is not allowed. // Registering the same end point is not allowed.
CHECK_EXCEPTION_THROWN_WITH_MESSAGE( CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
server->Register(client1->GetLocalEndPointInfo(), noOpCallback); , server->Register(client1->GetLocalEndPointInfo(), noOpCallback);
"Duplicate end point found."); , "Duplicate end point found.");
auto client2 = std::make_shared<Interprocess::Connection::ConnectionMonitor>(); auto client2 =
std::make_shared<Interprocess::Connection::ConnectionMonitor>();
client2->Register(server->GetLocalEndPointInfo(), callback); client2->Register(server->GetLocalEndPointInfo(), callback);
server->Register(client2->GetLocalEndPointInfo(), noOpCallback); server->Register(client2->GetLocalEndPointInfo(), noOpCallback);
auto client3 = std::make_shared<Interprocess::Connection::ConnectionMonitor>(); auto client3 =
std::make_shared<Interprocess::Connection::ConnectionMonitor>();
client3->Register(server->GetLocalEndPointInfo(), callback); client3->Register(server->GetLocalEndPointInfo(), callback);
server->Register(client3->GetLocalEndPointInfo(), noOpCallback); server->Register(client3->GetLocalEndPointInfo(), noOpCallback);
@ -51,7 +52,7 @@ BOOST_AUTO_TEST_CASE(ConnectionMonitorTest)
auto client1EndPointInfo = client1->GetLocalEndPointInfo(); auto client1EndPointInfo = client1->GetLocalEndPointInfo();
client1.reset(); client1.reset();
{ {
std::unique_lock<std::mutex> guard{ lock }; std::unique_lock<std::mutex> guard{lock};
cv.wait(guard, [&] { return endPointsDisconnected.size() >= 1U; }); cv.wait(guard, [&] { return endPointsDisconnected.size() >= 1U; });
BOOST_REQUIRE_EQUAL(endPointsDisconnected.size(), 1U); BOOST_REQUIRE_EQUAL(endPointsDisconnected.size(), 1U);
BOOST_CHECK(endPointsDisconnected[0] == client1EndPointInfo); BOOST_CHECK(endPointsDisconnected[0] == client1EndPointInfo);
@ -59,11 +60,12 @@ BOOST_AUTO_TEST_CASE(ConnectionMonitorTest)
BOOST_CHECK_EQUAL(server->GetRemoteConnectionsCount(), 2U); BOOST_CHECK_EQUAL(server->GetRemoteConnectionsCount(), 2U);
} }
// Now kill server and check if both callbacks in client2 and client3 are called. // Now kill server and check if both callbacks in client2 and client3 are
// called.
auto serverEndPointInfo = server->GetLocalEndPointInfo(); auto serverEndPointInfo = server->GetLocalEndPointInfo();
server.reset(); server.reset();
{ {
std::unique_lock<std::mutex> guard{ lock }; std::unique_lock<std::mutex> guard{lock};
cv.wait(guard, [&] { return endPointsDisconnected.size() >= 2U; }); cv.wait(guard, [&] { return endPointsDisconnected.size() >= 2U; });
BOOST_REQUIRE_EQUAL(endPointsDisconnected.size(), 2U); BOOST_REQUIRE_EQUAL(endPointsDisconnected.size(), 2U);
BOOST_CHECK(endPointsDisconnected[0] == serverEndPointInfo); BOOST_CHECK(endPointsDisconnected[0] == serverEndPointInfo);

Просмотреть файл

@ -1,26 +1,23 @@
#include <atomic> #include <atomic>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include "Utils.h"
#include "L4/Epoch/EpochQueue.h"
#include "L4/Epoch/EpochActionManager.h" #include "L4/Epoch/EpochActionManager.h"
#include "L4/Epoch/EpochQueue.h"
#include "L4/LocalMemory/EpochManager.h" #include "L4/LocalMemory/EpochManager.h"
#include "L4/Log/PerfCounter.h" #include "L4/Log/PerfCounter.h"
#include "L4/Utils/Lock.h" #include "L4/Utils/Lock.h"
#include "Utils.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
BOOST_AUTO_TEST_SUITE(EpochManagerTests) BOOST_AUTO_TEST_SUITE(EpochManagerTests)
BOOST_AUTO_TEST_CASE(EpochRefManagerTest) BOOST_AUTO_TEST_CASE(EpochRefManagerTest) {
{
std::uint64_t currentEpochCounter = 5U; std::uint64_t currentEpochCounter = 5U;
const std::uint32_t c_epochQueueSize = 100U; const std::uint32_t c_epochQueueSize = 100U;
using EpochQueue = EpochQueue< using EpochQueue =
boost::shared_lock_guard<L4::Utils::ReaderWriterLockSlim>, EpochQueue<boost::shared_lock_guard<L4::Utils::ReaderWriterLockSlim>,
std::lock_guard<L4::Utils::ReaderWriterLockSlim>>; std::lock_guard<L4::Utils::ReaderWriterLockSlim>>;
EpochQueue epochQueue(currentEpochCounter, c_epochQueueSize); EpochQueue epochQueue(currentEpochCounter, c_epochQueueSize);
@ -32,7 +29,8 @@ BOOST_AUTO_TEST_CASE(EpochRefManagerTest)
BOOST_CHECK_EQUAL(epochManager.AddRef(), currentEpochCounter); BOOST_CHECK_EQUAL(epochManager.AddRef(), currentEpochCounter);
// Validate that a reference count is incremented at the current epoch counter. // Validate that a reference count is incremented at the current epoch
// counter.
BOOST_CHECK_EQUAL(epochQueue.m_refCounts[currentEpochCounter], 1U); BOOST_CHECK_EQUAL(epochQueue.m_refCounts[currentEpochCounter], 1U);
epochManager.RemoveRef(currentEpochCounter); epochManager.RemoveRef(currentEpochCounter);
@ -40,29 +38,29 @@ BOOST_AUTO_TEST_CASE(EpochRefManagerTest)
// Validate that a reference count is back to 0. // Validate that a reference count is back to 0.
BOOST_CHECK_EQUAL(epochQueue.m_refCounts[currentEpochCounter], 0U); BOOST_CHECK_EQUAL(epochQueue.m_refCounts[currentEpochCounter], 0U);
// Decrementing a reference counter when it is already 0 will result in an exception. // Decrementing a reference counter when it is already 0 will result in an
// exception.
CHECK_EXCEPTION_THROWN_WITH_MESSAGE( CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
epochManager.RemoveRef(currentEpochCounter);, epochManager.RemoveRef(currentEpochCounter);
"Reference counter is invalid."); , "Reference counter is invalid.");
} }
BOOST_AUTO_TEST_CASE(EpochCounterManagerTest) {
BOOST_AUTO_TEST_CASE(EpochCounterManagerTest)
{
std::uint64_t currentEpochCounter = 0U; std::uint64_t currentEpochCounter = 0U;
const std::uint32_t c_epochQueueSize = 100U; const std::uint32_t c_epochQueueSize = 100U;
using EpochQueue = EpochQueue< using EpochQueue =
boost::shared_lock_guard<L4::Utils::ReaderWriterLockSlim>, EpochQueue<boost::shared_lock_guard<L4::Utils::ReaderWriterLockSlim>,
std::lock_guard<L4::Utils::ReaderWriterLockSlim>>; std::lock_guard<L4::Utils::ReaderWriterLockSlim>>;
EpochQueue epochQueue(currentEpochCounter, c_epochQueueSize); EpochQueue epochQueue(currentEpochCounter, c_epochQueueSize);
EpochCounterManager<EpochQueue> epochCounterManager(epochQueue); EpochCounterManager<EpochQueue> epochCounterManager(epochQueue);
// If RemoveUnreferenceEpochCounters() is called when m_fonrtIndex and m_backIndex are // If RemoveUnreferenceEpochCounters() is called when m_fonrtIndex and
// the same, it will just return either value. // m_backIndex are the same, it will just return either value.
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(), currentEpochCounter); BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(),
currentEpochCounter);
// Add two epoch counts. // Add two epoch counts.
++currentEpochCounter; ++currentEpochCounter;
@ -76,7 +74,8 @@ BOOST_AUTO_TEST_CASE(EpochCounterManagerTest)
// Since the m_frontIndex's reference count was zero, it will be incremented // Since the m_frontIndex's reference count was zero, it will be incremented
// all the way to currentEpochCounter. // all the way to currentEpochCounter.
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(), currentEpochCounter); BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(),
currentEpochCounter);
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter); BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter);
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter); BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
@ -86,9 +85,11 @@ BOOST_AUTO_TEST_CASE(EpochCounterManagerTest)
const auto epochCounterReferenced = epochRefManager.AddRef(); const auto epochCounterReferenced = epochRefManager.AddRef();
BOOST_CHECK_EQUAL(epochCounterReferenced, currentEpochCounter); BOOST_CHECK_EQUAL(epochCounterReferenced, currentEpochCounter);
// Calling RemoveUnreferenceEpochCounters() should just return currentEpochCounter // Calling RemoveUnreferenceEpochCounters() should just return
// since m_frontIndex and m_backIndex is the same. (Not affected by adding a reference yet). // currentEpochCounter since m_frontIndex and m_backIndex is the same. (Not
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(), currentEpochCounter); // affected by adding a reference yet).
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(),
currentEpochCounter);
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter); BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter);
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter); BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
@ -96,25 +97,25 @@ BOOST_AUTO_TEST_CASE(EpochCounterManagerTest)
++currentEpochCounter; ++currentEpochCounter;
epochCounterManager.AddNewEpoch(); epochCounterManager.AddNewEpoch();
// Now RemoveUnreferenceEpochCounters() should return epochCounterReferenced because // Now RemoveUnreferenceEpochCounters() should return epochCounterReferenced
// of the reference count. // because of the reference count.
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(), epochCounterReferenced); BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(),
epochCounterReferenced);
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, epochCounterReferenced); BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, epochCounterReferenced);
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter); BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
// Remove the reference. // Remove the reference.
epochRefManager.RemoveRef(epochCounterReferenced); epochRefManager.RemoveRef(epochCounterReferenced);
// Now RemoveUnreferenceEpochCounters() should return currentEpochCounter and m_frontIndex // Now RemoveUnreferenceEpochCounters() should return currentEpochCounter and
// should be in sync with m_backIndex. // m_frontIndex should be in sync with m_backIndex.
BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(), currentEpochCounter); BOOST_CHECK_EQUAL(epochCounterManager.RemoveUnreferenceEpochCounters(),
currentEpochCounter);
BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter); BOOST_CHECK_EQUAL(epochQueue.m_frontIndex, currentEpochCounter);
BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter); BOOST_CHECK_EQUAL(epochQueue.m_backIndex, currentEpochCounter);
} }
BOOST_AUTO_TEST_CASE(EpochActionManagerTest) {
BOOST_AUTO_TEST_CASE(EpochActionManagerTest)
{
EpochActionManager actionManager(2U); EpochActionManager actionManager(2U);
bool isAction1Called = false; bool isAction1Called = false;
@ -142,15 +143,12 @@ BOOST_AUTO_TEST_CASE(EpochActionManagerTest)
BOOST_CHECK(isAction1Called && isAction2Called); BOOST_CHECK(isAction1Called && isAction2Called);
} }
BOOST_AUTO_TEST_CASE(EpochManagerTest) {
BOOST_AUTO_TEST_CASE(EpochManagerTest)
{
ServerPerfData perfData; ServerPerfData perfData;
LocalMemory::EpochManager epochManager( LocalMemory::EpochManager epochManager(
EpochManagerConfig(100000U, std::chrono::milliseconds(5U), 1U), EpochManagerConfig(100000U, std::chrono::milliseconds(5U), 1U), perfData);
perfData);
std::atomic<bool> isActionCalled{ false }; std::atomic<bool> isActionCalled{false};
auto action = [&]() { isActionCalled = true; }; auto action = [&]() { isActionCalled = true; };
auto epochCounterReferenced = epochManager.GetEpochRefManager().AddRef(); auto epochCounterReferenced = epochManager.GetEpochRefManager().AddRef();
@ -158,13 +156,16 @@ BOOST_AUTO_TEST_CASE(EpochManagerTest)
epochManager.RegisterAction(action); epochManager.RegisterAction(action);
// Justification for using sleep_for in unit tests: // Justification for using sleep_for in unit tests:
// - EpochManager already uses an internal thread which wakes up and perform a task // - EpochManager already uses an internal thread which wakes up and perform a
// in a given interval and when the class is destroyed, there is a mechanism for // task in a given interval and when the class is destroyed, there is a
// waiting for the thread anyway. It's more crucial to test the end to end scenario this way. // mechanism for waiting for the thread anyway. It's more crucial to test the
// end to end scenario this way.
// - The overall execution time for this test is less than 50 milliseconds. // - The overall execution time for this test is less than 50 milliseconds.
auto initialEpochCounter = perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue); auto initialEpochCounter =
while (perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue) - initialEpochCounter < 2) perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue);
{ while (perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue) -
initialEpochCounter <
2) {
std::this_thread::sleep_for(std::chrono::milliseconds(5)); std::this_thread::sleep_for(std::chrono::milliseconds(5));
} }
@ -172,9 +173,11 @@ BOOST_AUTO_TEST_CASE(EpochManagerTest)
epochManager.GetEpochRefManager().RemoveRef(epochCounterReferenced); epochManager.GetEpochRefManager().RemoveRef(epochCounterReferenced);
initialEpochCounter = perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue); initialEpochCounter =
while (perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue) - initialEpochCounter < 2) perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue);
{ while (perfData.Get(ServerPerfCounter::LatestEpochCounterInQueue) -
initialEpochCounter <
2) {
std::this_thread::sleep_for(std::chrono::milliseconds(5)); std::this_thread::sleep_for(std::chrono::milliseconds(5));
} }

Просмотреть файл

@ -1,29 +1,28 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include "Utils.h"
#include "Mocks.h"
#include "L4/HashTable/Config.h" #include "L4/HashTable/Config.h"
#include "L4/HashTable/IHashTable.h" #include "L4/HashTable/IHashTable.h"
#include "L4/LocalMemory/HashTableManager.h" #include "L4/LocalMemory/HashTableManager.h"
#include "Mocks.h"
#include "Utils.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
class HashTableManagerTestsFixture class HashTableManagerTestsFixture {
{ protected:
protected:
template <typename Store> template <typename Store>
void ValidateRecord( void ValidateRecord(const Store& store,
const Store& store,
const char* expectedKeyStr, const char* expectedKeyStr,
const char* expectedValueStr) const char* expectedValueStr) {
{
IReadOnlyHashTable::Value actualValue; IReadOnlyHashTable::Value actualValue;
auto expectedValue = Utils::ConvertFromString<IReadOnlyHashTable::Value>(expectedValueStr); auto expectedValue =
BOOST_CHECK(store.Get(Utils::ConvertFromString<IReadOnlyHashTable::Key>(expectedKeyStr), actualValue)); Utils::ConvertFromString<IReadOnlyHashTable::Value>(expectedValueStr);
BOOST_CHECK(store.Get(
Utils::ConvertFromString<IReadOnlyHashTable::Key>(expectedKeyStr),
actualValue));
BOOST_CHECK(actualValue.m_size == expectedValue.m_size); BOOST_CHECK(actualValue.m_size == expectedValue.m_size);
BOOST_CHECK(!memcmp(actualValue.m_data, expectedValue.m_data, expectedValue.m_size)); BOOST_CHECK(!memcmp(actualValue.m_data, expectedValue.m_data,
expectedValue.m_size));
} }
MockEpochManager m_epochManager; MockEpochManager m_epochManager;
@ -32,17 +31,14 @@ protected:
BOOST_FIXTURE_TEST_SUITE(HashTableManagerTests, HashTableManagerTestsFixture) BOOST_FIXTURE_TEST_SUITE(HashTableManagerTests, HashTableManagerTestsFixture)
BOOST_AUTO_TEST_CASE(HashTableManagerTest) BOOST_AUTO_TEST_CASE(HashTableManagerTest) {
{
LocalMemory::HashTableManager htManager; LocalMemory::HashTableManager htManager;
const auto ht1Index = htManager.Add( const auto ht1Index = htManager.Add(
HashTableConfig("HashTable1", HashTableConfig::Setting(100U)), HashTableConfig("HashTable1", HashTableConfig::Setting(100U)),
m_epochManager, m_epochManager, m_allocator);
m_allocator);
const auto ht2Index = htManager.Add( const auto ht2Index = htManager.Add(
HashTableConfig("HashTable2", HashTableConfig::Setting(200U)), HashTableConfig("HashTable2", HashTableConfig::Setting(200U)),
m_epochManager, m_epochManager, m_allocator);
m_allocator);
{ {
auto& hashTable1 = htManager.GetHashTable("HashTable1"); auto& hashTable1 = htManager.GetHashTable("HashTable1");
@ -56,29 +52,20 @@ BOOST_AUTO_TEST_CASE(HashTableManagerTest)
Utils::ConvertFromString<IReadOnlyHashTable::Value>("HashTable2Value")); Utils::ConvertFromString<IReadOnlyHashTable::Value>("HashTable2Value"));
} }
ValidateRecord( ValidateRecord(htManager.GetHashTable(ht1Index), "HashTable1Key",
htManager.GetHashTable(ht1Index),
"HashTable1Key",
"HashTable1Value"); "HashTable1Value");
ValidateRecord( ValidateRecord(htManager.GetHashTable(ht2Index), "HashTable2Key",
htManager.GetHashTable(ht2Index),
"HashTable2Key",
"HashTable2Value"); "HashTable2Value");
} }
BOOST_AUTO_TEST_CASE(HashTableManagerTestForSerialzation) {
BOOST_AUTO_TEST_CASE(HashTableManagerTestForSerialzation) HashTableConfig htConfig{"HashTable1", HashTableConfig::Setting(100U)};
{
HashTableConfig htConfig{ "HashTable1", HashTableConfig::Setting(100U) };
std::ostringstream outStream; std::ostringstream outStream;
std::vector<std::pair<std::string, std::string>> testData; std::vector<std::pair<std::string, std::string>> testData;
for (std::int32_t i = 0; i < 10; ++i) for (std::int32_t i = 0; i < 10; ++i) {
{ testData.emplace_back("key" + std::to_string(i), "val" + std::to_string(i));
testData.emplace_back(
"key" + std::to_string(i),
"val" + std::to_string(i));
} }
// Serialize a hash table. // Serialize a hash table.
@ -88,11 +75,11 @@ BOOST_AUTO_TEST_CASE(HashTableManagerTestForSerialzation)
auto& hashTable1 = htManager.GetHashTable("HashTable1"); auto& hashTable1 = htManager.GetHashTable("HashTable1");
for (const auto& kvPair : testData) for (const auto& kvPair : testData) {
{ hashTable1.Add(Utils::ConvertFromString<IReadOnlyHashTable::Key>(
hashTable1.Add( kvPair.first.c_str()),
Utils::ConvertFromString<IReadOnlyHashTable::Key>(kvPair.first.c_str()), Utils::ConvertFromString<IReadOnlyHashTable::Value>(
Utils::ConvertFromString<IReadOnlyHashTable::Value>(kvPair.second.c_str())); kvPair.second.c_str()));
} }
auto serializer = hashTable1.GetSerializer(); auto serializer = hashTable1.GetSerializer();
@ -112,12 +99,8 @@ BOOST_AUTO_TEST_CASE(HashTableManagerTestForSerialzation)
hashTable1.GetPerfData().Get(HashTablePerfCounter::RecordsCount), hashTable1.GetPerfData().Get(HashTablePerfCounter::RecordsCount),
testData.size()); testData.size());
for (const auto& kvPair : testData) for (const auto& kvPair : testData) {
{ ValidateRecord(hashTable1, kvPair.first.c_str(), kvPair.second.c_str());
ValidateRecord(
hashTable1,
kvPair.first.c_str(),
kvPair.second.c_str());
} }
} }
} }

Просмотреть файл

@ -1,58 +1,60 @@
#include <boost/test/unit_test.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/test/unit_test.hpp>
#include <string> #include <string>
#include <vector> #include <vector>
#include "L4/HashTable/Common/Record.h" #include "L4/HashTable/Common/Record.h"
#include "Utils.h" #include "Utils.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
using namespace HashTable; using namespace HashTable;
class HashTableRecordTestFixture class HashTableRecordTestFixture {
{ protected:
protected: void Run(bool isFixedKey, bool isFixedValue, bool useMetaValue) {
void Run(bool isFixedKey, bool isFixedValue, bool useMetaValue) BOOST_TEST_MESSAGE("Running with isFixedKey="
{ << isFixedKey << ", isFixedValue=" << isFixedValue
BOOST_TEST_MESSAGE(
"Running with isFixedKey=" << isFixedKey
<< ", isFixedValue=" << isFixedValue
<< ", useMetatValue=" << useMetaValue); << ", useMetatValue=" << useMetaValue);
const std::string key = "TestKey"; const std::string key = "TestKey";
const std::string value = "TestValue"; const std::string value = "TestValue";
const std::string metaValue = "TestMetavalue"; const std::string metaValue = "TestMetavalue";
const auto recordOverhead = (isFixedKey ? 0U : c_keyTypeSize) + (isFixedValue ? 0U : c_valueTypeSize); const auto recordOverhead = (isFixedKey ? 0U : c_keyTypeSize) +
(isFixedValue ? 0U : c_valueTypeSize);
Validate( Validate(
RecordSerializer{ RecordSerializer{
isFixedKey ? static_cast<RecordSerializer::KeySize>(key.size()) : std::uint16_t(0), isFixedKey ? static_cast<RecordSerializer::KeySize>(key.size())
isFixedValue ? static_cast<RecordSerializer::ValueSize>(value.size()) : 0U, : std::uint16_t(0),
useMetaValue ? static_cast<RecordSerializer::ValueSize>(metaValue.size()) : 0U }, isFixedValue
key, ? static_cast<RecordSerializer::ValueSize>(value.size())
value, : 0U,
recordOverhead + key.size() + value.size() + (useMetaValue ? metaValue.size() : 0U), useMetaValue
? static_cast<RecordSerializer::ValueSize>(metaValue.size())
: 0U},
key, value,
recordOverhead + key.size() + value.size() +
(useMetaValue ? metaValue.size() : 0U),
recordOverhead, recordOverhead,
useMetaValue ? boost::optional<const std::string&>{ metaValue } : boost::none); useMetaValue ? boost::optional<const std::string&>{metaValue}
: boost::none);
} }
private: private:
void Validate( void Validate(const RecordSerializer& serializer,
const RecordSerializer& serializer,
const std::string& keyStr, const std::string& keyStr,
const std::string& valueStr, const std::string& valueStr,
std::size_t expectedBufferSize, std::size_t expectedBufferSize,
std::size_t expectedRecordOverheadSize, std::size_t expectedRecordOverheadSize,
boost::optional<const std::string&> metadataStr = boost::none) boost::optional<const std::string&> metadataStr = boost::none) {
{ BOOST_CHECK_EQUAL(serializer.CalculateRecordOverhead(),
BOOST_CHECK_EQUAL(serializer.CalculateRecordOverhead(), expectedRecordOverheadSize); expectedRecordOverheadSize);
const auto key = Utils::ConvertFromString<Record::Key>(keyStr.c_str()); const auto key = Utils::ConvertFromString<Record::Key>(keyStr.c_str());
const auto value = Utils::ConvertFromString<Record::Value>(valueStr.c_str()); const auto value =
Utils::ConvertFromString<Record::Value>(valueStr.c_str());
const auto bufferSize = serializer.CalculateBufferSize(key, value); const auto bufferSize = serializer.CalculateBufferSize(key, value);
@ -61,14 +63,14 @@ private:
RecordBuffer* recordBuffer = nullptr; RecordBuffer* recordBuffer = nullptr;
if (metadataStr) if (metadataStr) {
{ auto metaValue =
auto metaValue = Utils::ConvertFromString<Record::Value>(metadataStr->c_str()); Utils::ConvertFromString<Record::Value>(metadataStr->c_str());
recordBuffer = serializer.Serialize(key, value, metaValue, buffer.data(), bufferSize); recordBuffer = serializer.Serialize(key, value, metaValue, buffer.data(),
} bufferSize);
else } else {
{ recordBuffer =
recordBuffer = serializer.Serialize(key, value, buffer.data(), bufferSize); serializer.Serialize(key, value, buffer.data(), bufferSize);
} }
const auto record = serializer.Deserialize(*recordBuffer); const auto record = serializer.Deserialize(*recordBuffer);
@ -78,42 +80,34 @@ private:
BOOST_CHECK(record.m_value.m_data != value.m_data); BOOST_CHECK(record.m_value.m_data != value.m_data);
BOOST_CHECK(record.m_key == key); BOOST_CHECK(record.m_key == key);
if (metadataStr) if (metadataStr) {
{
const std::string newValueStr = *metadataStr + valueStr; const std::string newValueStr = *metadataStr + valueStr;
const auto newValue = Utils::ConvertFromString<Record::Value>(newValueStr.c_str()); const auto newValue =
Utils::ConvertFromString<Record::Value>(newValueStr.c_str());
BOOST_CHECK(record.m_value == newValue); BOOST_CHECK(record.m_value == newValue);
} } else {
else
{
BOOST_CHECK(record.m_value == value); BOOST_CHECK(record.m_value == value);
} }
} }
static constexpr std::size_t c_keyTypeSize = sizeof(Record::Key::size_type); static constexpr std::size_t c_keyTypeSize = sizeof(Record::Key::size_type);
static constexpr std::size_t c_valueTypeSize = sizeof(Record::Value::size_type); static constexpr std::size_t c_valueTypeSize =
sizeof(Record::Value::size_type);
}; };
BOOST_FIXTURE_TEST_SUITE(HashTableRecordTests, HashTableRecordTestFixture) BOOST_FIXTURE_TEST_SUITE(HashTableRecordTests, HashTableRecordTestFixture)
BOOST_AUTO_TEST_CASE(RunAll) BOOST_AUTO_TEST_CASE(RunAll) {
{
// Run all permutations for Run(), which takes three booleans. // Run all permutations for Run(), which takes three booleans.
for (int i = 0; i < 8; ++i) for (int i = 0; i < 8; ++i) {
{ Run(!!((i >> 2) & 1), !!((i >> 1) & 1), !!((i)&1));
Run(
!!((i >> 2) & 1),
!!((i >> 1) & 1),
!!((i) & 1));
} }
} }
BOOST_AUTO_TEST_CASE(InvalidSizeTest) {
BOOST_AUTO_TEST_CASE(InvalidSizeTest)
{
std::vector<std::uint8_t> buffer(100U); std::vector<std::uint8_t> buffer(100U);
RecordSerializer serializer{ 4, 5 }; RecordSerializer serializer{4, 5};
const std::string keyStr = "1234"; const std::string keyStr = "1234";
const std::string invalidStr = "999999"; const std::string invalidStr = "999999";
@ -122,8 +116,10 @@ BOOST_AUTO_TEST_CASE(InvalidSizeTest)
const auto key = Utils::ConvertFromString<Record::Key>(keyStr.c_str()); const auto key = Utils::ConvertFromString<Record::Key>(keyStr.c_str());
const auto value = Utils::ConvertFromString<Record::Value>(valueStr.c_str()); const auto value = Utils::ConvertFromString<Record::Value>(valueStr.c_str());
const auto invalidKey = Utils::ConvertFromString<Record::Key>(invalidStr.c_str()); const auto invalidKey =
const auto invalidValue = Utils::ConvertFromString<Record::Value>(invalidStr.c_str()); Utils::ConvertFromString<Record::Key>(invalidStr.c_str());
const auto invalidValue =
Utils::ConvertFromString<Record::Value>(invalidStr.c_str());
CHECK_EXCEPTION_THROWN_WITH_MESSAGE( CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
serializer.Serialize(invalidKey, value, buffer.data(), buffer.size()), serializer.Serialize(invalidKey, value, buffer.data(), buffer.size()),
@ -134,26 +130,28 @@ BOOST_AUTO_TEST_CASE(InvalidSizeTest)
"Invalid key or value sizes are given."); "Invalid key or value sizes are given.");
CHECK_EXCEPTION_THROWN_WITH_MESSAGE( CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
serializer.Serialize(invalidKey, invalidValue, buffer.data(), buffer.size()), serializer.Serialize(invalidKey, invalidValue, buffer.data(),
buffer.size()),
"Invalid key or value sizes are given."); "Invalid key or value sizes are given.");
// Normal case shouldn't thrown an exception. // Normal case shouldn't thrown an exception.
serializer.Serialize(key, value, buffer.data(), buffer.size()); serializer.Serialize(key, value, buffer.data(), buffer.size());
RecordSerializer serializerWithMetaValue{ 4, 5, 2 }; RecordSerializer serializerWithMetaValue{4, 5, 2};
std::uint16_t metadata = 0; std::uint16_t metadata = 0;
Record::Value metaValue{ Record::Value metaValue{reinterpret_cast<std::uint8_t*>(&metadata),
reinterpret_cast<std::uint8_t*>(&metadata), sizeof(metadata)};
sizeof(metadata) };
// Normal case shouldn't thrown an exception. // Normal case shouldn't thrown an exception.
serializerWithMetaValue.Serialize(key, value, metaValue, buffer.data(), buffer.size()); serializerWithMetaValue.Serialize(key, value, metaValue, buffer.data(),
buffer.size());
// Mismatching size is given. // Mismatching size is given.
metaValue.m_size = 1; metaValue.m_size = 1;
CHECK_EXCEPTION_THROWN_WITH_MESSAGE( CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
serializerWithMetaValue.Serialize(key, value, metaValue, buffer.data(), buffer.size()), serializerWithMetaValue.Serialize(key, value, metaValue, buffer.data(),
buffer.size()),
"Invalid meta value size is given."); "Invalid meta value size is given.");
} }

Просмотреть файл

@ -1,44 +1,38 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "L4/LocalMemory/HashTableService.h"
#include "Mocks.h" #include "Mocks.h"
#include "Utils.h" #include "Utils.h"
#include "L4/LocalMemory/HashTableService.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
BOOST_AUTO_TEST_CASE(HashTableServiceTest) BOOST_AUTO_TEST_CASE(HashTableServiceTest) {
{
std::vector<std::pair<std::string, std::string>> dataSet; std::vector<std::pair<std::string, std::string>> dataSet;
for (std::uint16_t i = 0U; i < 100; ++i) for (std::uint16_t i = 0U; i < 100; ++i) {
{ dataSet.emplace_back("key" + std::to_string(i),
dataSet.emplace_back("key" + std::to_string(i), "value" + std::to_string(i)); "value" + std::to_string(i));
} }
LocalMemory::HashTableService htService; LocalMemory::HashTableService htService;
htService.AddHashTable( htService.AddHashTable(
HashTableConfig("Table1", HashTableConfig::Setting{ 100U })); HashTableConfig("Table1", HashTableConfig::Setting{100U}));
htService.AddHashTable( htService.AddHashTable(HashTableConfig(
HashTableConfig( "Table2", HashTableConfig::Setting{1000U},
"Table2", HashTableConfig::Cache{1024, std::chrono::seconds{1U}, false}));
HashTableConfig::Setting{ 1000U },
HashTableConfig::Cache{ 1024, std::chrono::seconds{ 1U }, false }));
for (const auto& data : dataSet) for (const auto& data : dataSet) {
{
htService.GetContext()["Table1"].Add( htService.GetContext()["Table1"].Add(
Utils::ConvertFromString<IReadOnlyHashTable::Key>(data.first.c_str()), Utils::ConvertFromString<IReadOnlyHashTable::Key>(data.first.c_str()),
Utils::ConvertFromString<IReadOnlyHashTable::Value>(data.second.c_str())); Utils::ConvertFromString<IReadOnlyHashTable::Value>(
data.second.c_str()));
} }
// Smoke tests for looking up the data . // Smoke tests for looking up the data .
{ {
auto context = htService.GetContext(); auto context = htService.GetContext();
for (const auto& data : dataSet) for (const auto& data : dataSet) {
{
IReadOnlyHashTable::Value val; IReadOnlyHashTable::Value val;
BOOST_CHECK(context["Table1"].Get( BOOST_CHECK(context["Table1"].Get(
Utils::ConvertFromString<IReadOnlyHashTable::Key>(data.first.c_str()), Utils::ConvertFromString<IReadOnlyHashTable::Key>(data.first.c_str()),

Просмотреть файл

@ -3,28 +3,17 @@
#include "L4/Epoch/IEpochActionManager.h" #include "L4/Epoch/IEpochActionManager.h"
#include "L4/Log/PerfLogger.h" #include "L4/Log/PerfLogger.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
class MockPerfLogger : public IPerfLogger class MockPerfLogger : public IPerfLogger {
{ virtual void Log(const IData& data) override { (void)data; }
virtual void Log(const IData& data) override
{
(void)data;
}
}; };
struct MockEpochManager : public IEpochActionManager struct MockEpochManager : public IEpochActionManager {
{ MockEpochManager() : m_numRegisterActionsCalled(0) {}
MockEpochManager()
: m_numRegisterActionsCalled(0)
{
}
virtual void RegisterAction(Action&& action) override virtual void RegisterAction(Action&& action) override {
{
++m_numRegisterActionsCalled; ++m_numRegisterActionsCalled;
action(); action();
}; };

Просмотреть файл

@ -2,26 +2,19 @@
#include <limits> #include <limits>
#include "L4/Log/PerfLogger.h" #include "L4/Log/PerfLogger.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
void CheckMinCounters(const HashTablePerfData& htPerfData) void CheckMinCounters(const HashTablePerfData& htPerfData) {
{
const auto maxValue = (std::numeric_limits<std::int64_t>::max)(); const auto maxValue = (std::numeric_limits<std::int64_t>::max)();
/// Check if the min counter values are correctly initialized to max value. /// Check if the min counter values are correctly initialized to max value.
BOOST_CHECK_EQUAL(htPerfData.Get(HashTablePerfCounter::MinValueSize), maxValue); BOOST_CHECK_EQUAL(htPerfData.Get(HashTablePerfCounter::MinValueSize),
maxValue);
BOOST_CHECK_EQUAL(htPerfData.Get(HashTablePerfCounter::MinKeySize), maxValue); BOOST_CHECK_EQUAL(htPerfData.Get(HashTablePerfCounter::MinKeySize), maxValue);
} }
BOOST_AUTO_TEST_CASE(PerfCountersTest) BOOST_AUTO_TEST_CASE(PerfCountersTest) {
{ enum class TestCounter { Counter = 0, Count };
enum class TestCounter
{
Counter = 0,
Count
};
PerfCounters<TestCounter> perfCounters; PerfCounters<TestCounter> perfCounters;
@ -55,9 +48,7 @@ BOOST_AUTO_TEST_CASE(PerfCountersTest)
BOOST_CHECK_EQUAL(perfCounters.Get(TestCounter::Counter), 1); BOOST_CHECK_EQUAL(perfCounters.Get(TestCounter::Counter), 1);
} }
BOOST_AUTO_TEST_CASE(PerfDataTest) {
BOOST_AUTO_TEST_CASE(PerfDataTest)
{
PerfData testPerfData; PerfData testPerfData;
BOOST_CHECK(testPerfData.GetHashTablesPerfData().empty()); BOOST_CHECK(testPerfData.GetHashTablesPerfData().empty());
@ -86,17 +77,20 @@ BOOST_AUTO_TEST_CASE(PerfDataTest)
{ {
auto htPerfDataIt = hashTablesPerfData.find("HT1"); auto htPerfDataIt = hashTablesPerfData.find("HT1");
BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end()); BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end());
BOOST_CHECK_EQUAL(htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 10); BOOST_CHECK_EQUAL(
htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 10);
} }
{ {
auto htPerfDataIt = hashTablesPerfData.find("HT2"); auto htPerfDataIt = hashTablesPerfData.find("HT2");
BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end()); BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end());
BOOST_CHECK_EQUAL(htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 20); BOOST_CHECK_EQUAL(
htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 20);
} }
{ {
auto htPerfDataIt = hashTablesPerfData.find("HT3"); auto htPerfDataIt = hashTablesPerfData.find("HT3");
BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end()); BOOST_REQUIRE(htPerfDataIt != hashTablesPerfData.end());
BOOST_CHECK_EQUAL(htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 30); BOOST_CHECK_EQUAL(
htPerfDataIt->second.get().Get(HashTablePerfCounter::TotalKeySize), 30);
} }
} }

Просмотреть файл

@ -1,18 +1,16 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <string>
#include <sstream> #include <sstream>
#include <string>
#include <vector> #include <vector>
#include "Utils.h"
#include "Mocks.h"
#include "L4/HashTable/ReadWrite/HashTable.h" #include "L4/HashTable/ReadWrite/HashTable.h"
#include "L4/HashTable/ReadWrite/Serializer.h" #include "L4/HashTable/ReadWrite/Serializer.h"
#include "L4/Log/PerfCounter.h"
#include "L4/LocalMemory/Memory.h" #include "L4/LocalMemory/Memory.h"
#include "L4/Log/PerfCounter.h"
#include "Mocks.h"
#include "Utils.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
using namespace HashTable::ReadWrite; using namespace HashTable::ReadWrite;
@ -32,23 +30,24 @@ void ValidateSerializer(
const KeyValuePairs& keyValuePairs, const KeyValuePairs& keyValuePairs,
const Utils::ExpectedCounterValues& expectedCounterValuesAfterLoad, const Utils::ExpectedCounterValues& expectedCounterValuesAfterLoad,
const Utils::ExpectedCounterValues& expectedCounterValuesAfterSerialization, const Utils::ExpectedCounterValues& expectedCounterValuesAfterSerialization,
const Utils::ExpectedCounterValues& expectedCounterValuesAfterDeserialization) const Utils::ExpectedCounterValues&
{ expectedCounterValuesAfterDeserialization) {
Memory memory; Memory memory;
MockEpochManager epochManager; MockEpochManager epochManager;
auto hashTableHolder{ auto hashTableHolder{memory.MakeUnique<HashTable>(HashTable::Setting{5},
memory.MakeUnique<HashTable>( memory.GetAllocator())};
HashTable::Setting{ 5 }, memory.GetAllocator()) };
BOOST_CHECK(hashTableHolder != nullptr); BOOST_CHECK(hashTableHolder != nullptr);
WritableHashTable<Allocator> writableHashTable(*hashTableHolder, epochManager); WritableHashTable<Allocator> writableHashTable(*hashTableHolder,
epochManager);
// Insert the given key/value pairs to the hash table. // Insert the given key/value pairs to the hash table.
for (const auto& pair : keyValuePairs) for (const auto& pair : keyValuePairs) {
{ auto key =
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(pair.first.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(pair.first.c_str());
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(pair.second.c_str()); auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(
pair.second.c_str());
writableHashTable.Add(key, val); writableHashTable.Add(key, val);
} }
@ -65,112 +64,90 @@ void ValidateSerializer(
// Read in the hash table from the stream and validate it. // Read in the hash table from the stream and validate it.
std::istringstream inStream(outStream.str()); std::istringstream inStream(outStream.str());
// version == 0 means that it's run through the HashTableSerializer, thus the following can be skipped. // version == 0 means that it's run through the HashTableSerializer, thus the
if (serializerVersion != 0) // following can be skipped.
{ if (serializerVersion != 0) {
std::uint8_t actualSerializerVersion = 0; std::uint8_t actualSerializerVersion = 0;
DeserializerHelper(inStream).Deserialize(actualSerializerVersion); DeserializerHelper(inStream).Deserialize(actualSerializerVersion);
BOOST_CHECK(actualSerializerVersion == serializerVersion); BOOST_CHECK(actualSerializerVersion == serializerVersion);
} } else {
else BOOST_REQUIRE(typeid(L4::HashTable::ReadWrite::Serializer<
{ HashTable, ReadOnlyHashTable>) == typeid(Serializer));
BOOST_REQUIRE(typeid(L4::HashTable::ReadWrite::Serializer<HashTable, ReadOnlyHashTable>) == typeid(Serializer));
} }
auto newHashTableHolder = deserializer.Deserialize(memory, inStream); auto newHashTableHolder = deserializer.Deserialize(memory, inStream);
BOOST_CHECK(newHashTableHolder != nullptr); BOOST_CHECK(newHashTableHolder != nullptr);
WritableHashTable<Allocator> newWritableHashTable(*newHashTableHolder, epochManager); WritableHashTable<Allocator> newWritableHashTable(*newHashTableHolder,
epochManager);
const auto& newPerfData = newWritableHashTable.GetPerfData(); const auto& newPerfData = newWritableHashTable.GetPerfData();
Utils::ValidateCounters(newPerfData, expectedCounterValuesAfterDeserialization); Utils::ValidateCounters(newPerfData,
expectedCounterValuesAfterDeserialization);
// Make sure all the key/value pairs exist after deserialization. // Make sure all the key/value pairs exist after deserialization.
for (const auto& pair : keyValuePairs) for (const auto& pair : keyValuePairs) {
{ auto key =
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(pair.first.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(pair.first.c_str());
IReadOnlyHashTable::Value val; IReadOnlyHashTable::Value val;
BOOST_CHECK(newWritableHashTable.Get(key, val)); BOOST_CHECK(newWritableHashTable.Get(key, val));
BOOST_CHECK(Utils::ConvertToString(val) == pair.second); BOOST_CHECK(Utils::ConvertToString(val) == pair.second);
} }
} }
BOOST_AUTO_TEST_CASE(CurrentSerializerTest) {
BOOST_AUTO_TEST_CASE(CurrentSerializerTest)
{
ValidateSerializer( ValidateSerializer(
Current::Serializer<HashTable, ReadOnlyHashTable>{}, Current::Serializer<HashTable, ReadOnlyHashTable>{},
Current::Deserializer<Memory, HashTable, WritableHashTable>{ L4::Utils::Properties{} }, Current::Deserializer<Memory, HashTable, WritableHashTable>{
L4::Utils::Properties{}},
Current::c_version, Current::c_version,
{ {{"hello1", " world1"}, {"hello2", " world2"}, {"hello3", " world3"}},
{ "hello1", " world1" }, {{HashTablePerfCounter::RecordsCount, 3},
{ "hello2", " world2" }, {HashTablePerfCounter::BucketsCount, 5},
{ "hello3", " world3" } {HashTablePerfCounter::TotalKeySize, 18},
}, {HashTablePerfCounter::TotalValueSize, 21},
{ {HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0},
{ HashTablePerfCounter::RecordsCount, 3 }, {HashTablePerfCounter::RecordsCountSavedFromSerializer, 0}},
{ HashTablePerfCounter::BucketsCount, 5 }, {{HashTablePerfCounter::RecordsCount, 3},
{ HashTablePerfCounter::TotalKeySize, 18 }, {HashTablePerfCounter::BucketsCount, 5},
{ HashTablePerfCounter::TotalValueSize, 21 }, {HashTablePerfCounter::TotalKeySize, 18},
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0 }, {HashTablePerfCounter::TotalValueSize, 21},
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 0 } {HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0},
}, {HashTablePerfCounter::RecordsCountSavedFromSerializer, 3}},
{ {{HashTablePerfCounter::RecordsCount, 3},
{ HashTablePerfCounter::RecordsCount, 3 }, {HashTablePerfCounter::BucketsCount, 5},
{ HashTablePerfCounter::BucketsCount, 5 }, {HashTablePerfCounter::TotalKeySize, 18},
{ HashTablePerfCounter::TotalKeySize, 18 }, {HashTablePerfCounter::TotalValueSize, 21},
{ HashTablePerfCounter::TotalValueSize, 21 }, {HashTablePerfCounter::RecordsCountLoadedFromSerializer, 3},
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0 }, {HashTablePerfCounter::RecordsCountSavedFromSerializer, 0}});
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 3 }
},
{
{ HashTablePerfCounter::RecordsCount, 3 },
{ HashTablePerfCounter::BucketsCount, 5 },
{ HashTablePerfCounter::TotalKeySize, 18 },
{ HashTablePerfCounter::TotalValueSize, 21 },
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 3 },
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 0 }
});
} }
BOOST_AUTO_TEST_CASE(HashTableSerializeTest) {
BOOST_AUTO_TEST_CASE(HashTableSerializeTest)
{
// This test case tests end to end scenario using the HashTableSerializer. // This test case tests end to end scenario using the HashTableSerializer.
ValidateSerializer( ValidateSerializer(
Serializer<HashTable, ReadOnlyHashTable>{}, Serializer<HashTable, ReadOnlyHashTable>{},
Deserializer<Memory, HashTable, WritableHashTable>{ L4::Utils::Properties{} }, Deserializer<Memory, HashTable, WritableHashTable>{
0U, L4::Utils::Properties{}},
{ 0U, {{"hello1", " world1"}, {"hello2", " world2"}, {"hello3", " world3"}},
{ "hello1", " world1" }, {{HashTablePerfCounter::RecordsCount, 3},
{ "hello2", " world2" }, {HashTablePerfCounter::BucketsCount, 5},
{ "hello3", " world3" } {HashTablePerfCounter::TotalKeySize, 18},
}, {HashTablePerfCounter::TotalValueSize, 21},
{ {HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0},
{ HashTablePerfCounter::RecordsCount, 3 }, {HashTablePerfCounter::RecordsCountSavedFromSerializer, 0}},
{ HashTablePerfCounter::BucketsCount, 5 }, {{HashTablePerfCounter::RecordsCount, 3},
{ HashTablePerfCounter::TotalKeySize, 18 }, {HashTablePerfCounter::BucketsCount, 5},
{ HashTablePerfCounter::TotalValueSize, 21 }, {HashTablePerfCounter::TotalKeySize, 18},
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0 }, {HashTablePerfCounter::TotalValueSize, 21},
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 0 } {HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0},
}, {HashTablePerfCounter::RecordsCountSavedFromSerializer, 3}},
{ {{HashTablePerfCounter::RecordsCount, 3},
{ HashTablePerfCounter::RecordsCount, 3 }, {HashTablePerfCounter::BucketsCount, 5},
{ HashTablePerfCounter::BucketsCount, 5 }, {HashTablePerfCounter::TotalKeySize, 18},
{ HashTablePerfCounter::TotalKeySize, 18 }, {HashTablePerfCounter::TotalValueSize, 21},
{ HashTablePerfCounter::TotalValueSize, 21 }, {HashTablePerfCounter::RecordsCountLoadedFromSerializer, 3},
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 0 }, {HashTablePerfCounter::RecordsCountSavedFromSerializer, 0}});
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 3 }
},
{
{ HashTablePerfCounter::RecordsCount, 3 },
{ HashTablePerfCounter::BucketsCount, 5 },
{ HashTablePerfCounter::TotalKeySize, 18 },
{ HashTablePerfCounter::TotalValueSize, 21 },
{ HashTablePerfCounter::RecordsCountLoadedFromSerializer, 3 },
{ HashTablePerfCounter::RecordsCountSavedFromSerializer, 0 }
});
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

Просмотреть файл

@ -1,39 +1,30 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include "Utils.h"
#include "Mocks.h"
#include "CheckedAllocator.h" #include "CheckedAllocator.h"
#include "L4/Log/PerfCounter.h"
#include "L4/HashTable/ReadWrite/HashTable.h" #include "L4/HashTable/ReadWrite/HashTable.h"
#include "L4/Log/PerfCounter.h"
#include "Mocks.h"
#include "Utils.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
using namespace HashTable::ReadWrite; using namespace HashTable::ReadWrite;
class ReadWriteHashTableTestFixture class ReadWriteHashTableTestFixture {
{ protected:
protected:
using Allocator = CheckedAllocator<>; using Allocator = CheckedAllocator<>;
using HashTable = WritableHashTable<Allocator>::HashTable; using HashTable = WritableHashTable<Allocator>::HashTable;
ReadWriteHashTableTestFixture() ReadWriteHashTableTestFixture() : m_allocator{}, m_epochManager{} {}
: m_allocator{}
, m_epochManager{}
{}
Allocator m_allocator; Allocator m_allocator;
MockEpochManager m_epochManager; MockEpochManager m_epochManager;
}; };
BOOST_FIXTURE_TEST_SUITE(ReadWriteHashTableTests, ReadWriteHashTableTestFixture) BOOST_FIXTURE_TEST_SUITE(ReadWriteHashTableTests, ReadWriteHashTableTestFixture)
BOOST_AUTO_TEST_CASE(HashTableTest) {
BOOST_AUTO_TEST_CASE(HashTableTest) HashTable hashTable{HashTable::Setting{100, 5}, m_allocator};
{
HashTable hashTable{ HashTable::Setting{ 100, 5 }, m_allocator };
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager); WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable); ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
@ -43,35 +34,35 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
// Check empty data. // Check empty data.
std::string keyStr = "hello"; std::string keyStr = "hello";
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
IReadOnlyHashTable::Value data; IReadOnlyHashTable::Value data;
BOOST_CHECK(!readOnlyHashTable.Get(key, data)); BOOST_CHECK(!readOnlyHashTable.Get(key, data));
const auto c_counterMaxValue = (std::numeric_limits<HashTablePerfData::TValue>::max)(); const auto c_counterMaxValue =
(std::numeric_limits<HashTablePerfData::TValue>::max)();
Utils::ValidateCounters( Utils::ValidateCounters(
perfData, perfData, {{HashTablePerfCounter::RecordsCount, 0},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 0 }, {HashTablePerfCounter::ChainingEntriesCount, 0},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalKeySize, 0},
{ HashTablePerfCounter::ChainingEntriesCount, 0 }, {HashTablePerfCounter::TotalValueSize, 0},
{ HashTablePerfCounter::TotalKeySize, 0 }, {HashTablePerfCounter::MinKeySize, c_counterMaxValue},
{ HashTablePerfCounter::TotalValueSize, 0 }, {HashTablePerfCounter::MaxKeySize, 0},
{ HashTablePerfCounter::MinKeySize, c_counterMaxValue }, {HashTablePerfCounter::MinValueSize, c_counterMaxValue},
{ HashTablePerfCounter::MaxKeySize, 0 }, {HashTablePerfCounter::MaxValueSize, 0}});
{ HashTablePerfCounter::MinValueSize, c_counterMaxValue },
{ HashTablePerfCounter::MaxValueSize, 0 }
});
} }
{ {
// First record added. // First record added.
std::string keyStr = "hello"; std::string keyStr = "hello";
std::string valStr = "world"; std::string valStr = "world";
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
auto val =
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
writableHashTable.Add(key, val); writableHashTable.Add(key, val);
@ -80,19 +71,16 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
BOOST_CHECK(value.m_size == valStr.size()); BOOST_CHECK(value.m_size == valStr.size());
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size())); BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData, {{HashTablePerfCounter::RecordsCount, 1},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 1 }, {HashTablePerfCounter::ChainingEntriesCount, 0},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalKeySize, 5},
{ HashTablePerfCounter::ChainingEntriesCount, 0 }, {HashTablePerfCounter::TotalValueSize, 5},
{ HashTablePerfCounter::TotalKeySize, 5 }, {HashTablePerfCounter::MinKeySize, 5},
{ HashTablePerfCounter::TotalValueSize, 5 }, {HashTablePerfCounter::MaxKeySize, 5},
{ HashTablePerfCounter::MinKeySize, 5 }, {HashTablePerfCounter::MinValueSize, 5},
{ HashTablePerfCounter::MaxKeySize, 5 }, {HashTablePerfCounter::MaxValueSize, 5}});
{ HashTablePerfCounter::MinValueSize, 5 },
{ HashTablePerfCounter::MaxValueSize, 5 }
});
} }
{ {
@ -100,8 +88,10 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
std::string keyStr = "hello2"; std::string keyStr = "hello2";
std::string valStr = "world2"; std::string valStr = "world2";
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
auto val =
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
writableHashTable.Add(key, val); writableHashTable.Add(key, val);
@ -110,19 +100,16 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
BOOST_CHECK(value.m_size == valStr.size()); BOOST_CHECK(value.m_size == valStr.size());
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size())); BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData, {{HashTablePerfCounter::RecordsCount, 2},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 2 }, {HashTablePerfCounter::ChainingEntriesCount, 0},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalKeySize, 11},
{ HashTablePerfCounter::ChainingEntriesCount, 0 }, {HashTablePerfCounter::TotalValueSize, 11},
{ HashTablePerfCounter::TotalKeySize, 11 }, {HashTablePerfCounter::MinKeySize, 5},
{ HashTablePerfCounter::TotalValueSize, 11 }, {HashTablePerfCounter::MaxKeySize, 6},
{ HashTablePerfCounter::MinKeySize, 5 }, {HashTablePerfCounter::MinValueSize, 5},
{ HashTablePerfCounter::MaxKeySize, 6 }, {HashTablePerfCounter::MaxValueSize, 6}});
{ HashTablePerfCounter::MinValueSize, 5 },
{ HashTablePerfCounter::MaxValueSize, 6 }
});
} }
{ {
@ -130,8 +117,10 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
std::string keyStr = "hello"; std::string keyStr = "hello";
std::string valStr = "world long string"; std::string valStr = "world long string";
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
auto val =
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
writableHashTable.Add(key, val); writableHashTable.Add(key, val);
@ -141,19 +130,16 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size())); BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 1); BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 1);
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData, {{HashTablePerfCounter::RecordsCount, 2},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 2 }, {HashTablePerfCounter::ChainingEntriesCount, 0},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalKeySize, 11},
{ HashTablePerfCounter::ChainingEntriesCount, 0 }, {HashTablePerfCounter::TotalValueSize, 23},
{ HashTablePerfCounter::TotalKeySize, 11 }, {HashTablePerfCounter::MinKeySize, 5},
{ HashTablePerfCounter::TotalValueSize, 23 }, {HashTablePerfCounter::MaxKeySize, 6},
{ HashTablePerfCounter::MinKeySize, 5 }, {HashTablePerfCounter::MinValueSize, 5},
{ HashTablePerfCounter::MaxKeySize, 6 }, {HashTablePerfCounter::MaxValueSize, 17}});
{ HashTablePerfCounter::MinValueSize, 5 },
{ HashTablePerfCounter::MaxValueSize, 17 }
});
} }
{ {
@ -161,8 +147,10 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
std::string keyStr = "hello2"; std::string keyStr = "hello2";
std::string valStr = "wo"; std::string valStr = "wo";
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
auto val =
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
writableHashTable.Add(key, val); writableHashTable.Add(key, val);
@ -172,19 +160,16 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size())); BOOST_CHECK(!memcmp(value.m_data, valStr.c_str(), valStr.size()));
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 2); BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 2);
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData, {{HashTablePerfCounter::RecordsCount, 2},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 2 }, {HashTablePerfCounter::ChainingEntriesCount, 0},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalKeySize, 11},
{ HashTablePerfCounter::ChainingEntriesCount, 0 }, {HashTablePerfCounter::TotalValueSize, 19},
{ HashTablePerfCounter::TotalKeySize, 11 }, {HashTablePerfCounter::MinKeySize, 5},
{ HashTablePerfCounter::TotalValueSize, 19 }, {HashTablePerfCounter::MaxKeySize, 6},
{ HashTablePerfCounter::MinKeySize, 5 }, {HashTablePerfCounter::MinValueSize, 2},
{ HashTablePerfCounter::MaxKeySize, 6 }, {HashTablePerfCounter::MaxValueSize, 17}});
{ HashTablePerfCounter::MinValueSize, 2 },
{ HashTablePerfCounter::MaxValueSize, 17 }
});
} }
{ {
@ -192,26 +177,25 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
std::string keyStr = "hello"; std::string keyStr = "hello";
std::string valStr = ""; std::string valStr = "";
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
auto val =
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
BOOST_CHECK(writableHashTable.Remove(key)); BOOST_CHECK(writableHashTable.Remove(key));
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 3); BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 3);
// Note that the Remove() doesn't change Min/Max counters by design. // Note that the Remove() doesn't change Min/Max counters by design.
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData, {{HashTablePerfCounter::RecordsCount, 1},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 1 }, {HashTablePerfCounter::ChainingEntriesCount, 0},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalKeySize, 6},
{ HashTablePerfCounter::ChainingEntriesCount, 0 }, {HashTablePerfCounter::TotalValueSize, 2},
{ HashTablePerfCounter::TotalKeySize, 6 }, {HashTablePerfCounter::MinKeySize, 5},
{ HashTablePerfCounter::TotalValueSize, 2 }, {HashTablePerfCounter::MaxKeySize, 6},
{ HashTablePerfCounter::MinKeySize, 5 }, {HashTablePerfCounter::MinValueSize, 2},
{ HashTablePerfCounter::MaxKeySize, 6 }, {HashTablePerfCounter::MaxValueSize, 17}});
{ HashTablePerfCounter::MinValueSize, 2 },
{ HashTablePerfCounter::MaxValueSize, 17 }
});
// Remove the second key. // Remove the second key.
keyStr = "hello2"; keyStr = "hello2";
@ -220,58 +204,51 @@ BOOST_AUTO_TEST_CASE(HashTableTest)
BOOST_CHECK(writableHashTable.Remove(key)); BOOST_CHECK(writableHashTable.Remove(key));
BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 4); BOOST_CHECK(m_epochManager.m_numRegisterActionsCalled == 4);
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData, {{HashTablePerfCounter::RecordsCount, 0},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 0 }, {HashTablePerfCounter::ChainingEntriesCount, 0},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalKeySize, 0},
{ HashTablePerfCounter::ChainingEntriesCount, 0 }, {HashTablePerfCounter::TotalValueSize, 0},
{ HashTablePerfCounter::TotalKeySize, 0 }, {HashTablePerfCounter::MinKeySize, 5},
{ HashTablePerfCounter::TotalValueSize, 0 }, {HashTablePerfCounter::MaxKeySize, 6},
{ HashTablePerfCounter::MinKeySize, 5 }, {HashTablePerfCounter::MinValueSize, 2},
{ HashTablePerfCounter::MaxKeySize, 6 }, {HashTablePerfCounter::MaxValueSize, 17}});
{ HashTablePerfCounter::MinValueSize, 2 },
{ HashTablePerfCounter::MaxValueSize, 17 }
});
// Removing the key that doesn't exist. // Removing the key that doesn't exist.
BOOST_CHECK(!writableHashTable.Remove(key)); BOOST_CHECK(!writableHashTable.Remove(key));
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData, {{HashTablePerfCounter::RecordsCount, 0},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 0 }, {HashTablePerfCounter::ChainingEntriesCount, 0},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalKeySize, 0},
{ HashTablePerfCounter::ChainingEntriesCount, 0 }, {HashTablePerfCounter::TotalValueSize, 0},
{ HashTablePerfCounter::TotalKeySize, 0 }, {HashTablePerfCounter::MinKeySize, 5},
{ HashTablePerfCounter::TotalValueSize, 0 }, {HashTablePerfCounter::MaxKeySize, 6},
{ HashTablePerfCounter::MinKeySize, 5 }, {HashTablePerfCounter::MinValueSize, 2},
{ HashTablePerfCounter::MaxKeySize, 6 }, {HashTablePerfCounter::MaxValueSize, 17}});
{ HashTablePerfCounter::MinValueSize, 2 },
{ HashTablePerfCounter::MaxValueSize, 17 }
});
} }
} }
BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest) {
BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
{
Allocator allocator; Allocator allocator;
HashTable hashTable{ HashTable::Setting{ 1 }, allocator }; HashTable hashTable{HashTable::Setting{1}, allocator};
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager); WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable); ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
const auto& perfData = writableHashTable.GetPerfData(); const auto& perfData = writableHashTable.GetPerfData();
Utils::ValidateCounters(perfData, { { HashTablePerfCounter::ChainingEntriesCount, 0 } }); Utils::ValidateCounters(perfData,
{{HashTablePerfCounter::ChainingEntriesCount, 0}});
const auto initialTotalIndexSize = perfData.Get(HashTablePerfCounter::TotalIndexSize); const auto initialTotalIndexSize =
perfData.Get(HashTablePerfCounter::TotalIndexSize);
const std::size_t c_dataSetSize = HashTable::Entry::c_numDataPerEntry + 5U; const std::size_t c_dataSetSize = HashTable::Entry::c_numDataPerEntry + 5U;
std::size_t expectedTotalKeySize = 0U; std::size_t expectedTotalKeySize = 0U;
std::size_t expectedTotalValueSize = 0U; std::size_t expectedTotalValueSize = 0U;
for (auto i = 0U; i < c_dataSetSize; ++i) for (auto i = 0U; i < c_dataSetSize; ++i) {
{
std::stringstream keyStream; std::stringstream keyStream;
keyStream << "key" << i; keyStream << "key" << i;
@ -281,8 +258,10 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
std::string keyStr = keyStream.str(); std::string keyStr = keyStream.str();
std::string valStr = valStream.str(); std::string valStr = valStream.str();
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
auto val =
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
expectedTotalKeySize += key.m_size; expectedTotalKeySize += key.m_size;
expectedTotalValueSize += val.m_size; expectedTotalValueSize += val.m_size;
@ -298,28 +277,24 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
using L4::HashTable::RecordSerializer; using L4::HashTable::RecordSerializer;
// Variable key/value sizes. // Variable key/value sizes.
const auto recordOverhead = RecordSerializer{ 0U, 0U }.CalculateRecordOverhead(); const auto recordOverhead =
RecordSerializer{0U, 0U}.CalculateRecordOverhead();
Utils::ValidateCounters( Utils::ValidateCounters(
perfData, perfData, {{HashTablePerfCounter::RecordsCount, c_dataSetSize},
{ {HashTablePerfCounter::BucketsCount, 1},
{ HashTablePerfCounter::RecordsCount, c_dataSetSize }, {HashTablePerfCounter::MaxBucketChainLength, 2},
{ HashTablePerfCounter::BucketsCount, 1 }, {HashTablePerfCounter::ChainingEntriesCount, 1},
{ HashTablePerfCounter::MaxBucketChainLength, 2 }, {HashTablePerfCounter::TotalKeySize, expectedTotalKeySize},
{ HashTablePerfCounter::ChainingEntriesCount, 1 }, {HashTablePerfCounter::TotalValueSize, expectedTotalValueSize},
{ HashTablePerfCounter::TotalKeySize, expectedTotalKeySize }, {HashTablePerfCounter::TotalIndexSize,
{ HashTablePerfCounter::TotalValueSize, expectedTotalValueSize }, initialTotalIndexSize + sizeof(HashTable::Entry) +
{ (c_dataSetSize * recordOverhead)}});
HashTablePerfCounter::TotalIndexSize,
initialTotalIndexSize + sizeof(HashTable::Entry) + (c_dataSetSize * recordOverhead)
}
});
// Now replace with new values. // Now replace with new values.
expectedTotalValueSize = 0U; expectedTotalValueSize = 0U;
for (auto i = 0U; i < c_dataSetSize; ++i) for (auto i = 0U; i < c_dataSetSize; ++i) {
{
std::stringstream keyStream; std::stringstream keyStream;
keyStream << "key" << i; keyStream << "key" << i;
@ -329,8 +304,10 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
std::string keyStr = keyStream.str(); std::string keyStr = keyStream.str();
std::string valStr = valStream.str(); std::string valStr = valStream.str();
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
auto val =
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
expectedTotalValueSize += val.m_size; expectedTotalValueSize += val.m_size;
@ -343,28 +320,24 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
} }
Utils::ValidateCounters( Utils::ValidateCounters(
perfData, perfData, {{HashTablePerfCounter::RecordsCount, c_dataSetSize},
{ {HashTablePerfCounter::BucketsCount, 1},
{ HashTablePerfCounter::RecordsCount, c_dataSetSize }, {HashTablePerfCounter::MaxBucketChainLength, 2},
{ HashTablePerfCounter::BucketsCount, 1 }, {HashTablePerfCounter::ChainingEntriesCount, 1},
{ HashTablePerfCounter::MaxBucketChainLength, 2 }, {HashTablePerfCounter::TotalKeySize, expectedTotalKeySize},
{ HashTablePerfCounter::ChainingEntriesCount, 1 }, {HashTablePerfCounter::TotalValueSize, expectedTotalValueSize},
{ HashTablePerfCounter::TotalKeySize, expectedTotalKeySize }, {HashTablePerfCounter::TotalIndexSize,
{ HashTablePerfCounter::TotalValueSize, expectedTotalValueSize }, initialTotalIndexSize + sizeof(HashTable::Entry) +
{ (c_dataSetSize * recordOverhead)}});
HashTablePerfCounter::TotalIndexSize,
initialTotalIndexSize + sizeof(HashTable::Entry) + (c_dataSetSize * recordOverhead)
}
});
// Now remove all key-value. // Now remove all key-value.
for (auto i = 0U; i < c_dataSetSize; ++i) for (auto i = 0U; i < c_dataSetSize; ++i) {
{
std::stringstream keyStream; std::stringstream keyStream;
keyStream << "key" << i; keyStream << "key" << i;
std::string keyStr = keyStream.str(); std::string keyStr = keyStream.str();
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
BOOST_CHECK(writableHashTable.Remove(key)); BOOST_CHECK(writableHashTable.Remove(key));
@ -372,27 +345,21 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
BOOST_CHECK(!readOnlyHashTable.Get(key, value)); BOOST_CHECK(!readOnlyHashTable.Get(key, value));
} }
Utils::ValidateCounters( Utils::ValidateCounters(perfData,
perfData, {{HashTablePerfCounter::RecordsCount, 0},
{ {HashTablePerfCounter::BucketsCount, 1},
{ HashTablePerfCounter::RecordsCount, 0 }, {HashTablePerfCounter::MaxBucketChainLength, 2},
{ HashTablePerfCounter::BucketsCount, 1 }, {HashTablePerfCounter::ChainingEntriesCount, 1},
{ HashTablePerfCounter::MaxBucketChainLength, 2 }, {HashTablePerfCounter::TotalKeySize, 0},
{ HashTablePerfCounter::ChainingEntriesCount, 1 }, {HashTablePerfCounter::TotalValueSize, 0},
{ HashTablePerfCounter::TotalKeySize, 0 }, {HashTablePerfCounter::TotalIndexSize,
{ HashTablePerfCounter::TotalValueSize, 0 }, initialTotalIndexSize + sizeof(HashTable::Entry)}});
{
HashTablePerfCounter::TotalIndexSize,
initialTotalIndexSize + sizeof(HashTable::Entry)
}
});
// Try to add back to the same bucket (reusing existing entries) // Try to add back to the same bucket (reusing existing entries)
expectedTotalKeySize = 0U; expectedTotalKeySize = 0U;
expectedTotalValueSize = 0U; expectedTotalValueSize = 0U;
for (auto i = 0U; i < c_dataSetSize; ++i) for (auto i = 0U; i < c_dataSetSize; ++i) {
{
std::stringstream keyStream; std::stringstream keyStream;
keyStream << "key" << i; keyStream << "key" << i;
@ -402,8 +369,10 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
std::string keyStr = keyStream.str(); std::string keyStr = keyStream.str();
std::string valStr = valStream.str(); std::string valStr = valStream.str();
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
auto val =
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
expectedTotalKeySize += key.m_size; expectedTotalKeySize += key.m_size;
expectedTotalValueSize += val.m_size; expectedTotalValueSize += val.m_size;
@ -417,25 +386,19 @@ BOOST_AUTO_TEST_CASE(HashTableWithOneBucketTest)
} }
Utils::ValidateCounters( Utils::ValidateCounters(
perfData, perfData, {{HashTablePerfCounter::RecordsCount, c_dataSetSize},
{ {HashTablePerfCounter::BucketsCount, 1},
{ HashTablePerfCounter::RecordsCount, c_dataSetSize }, {HashTablePerfCounter::MaxBucketChainLength, 2},
{ HashTablePerfCounter::BucketsCount, 1 }, {HashTablePerfCounter::ChainingEntriesCount, 1},
{ HashTablePerfCounter::MaxBucketChainLength, 2 }, {HashTablePerfCounter::TotalKeySize, expectedTotalKeySize},
{ HashTablePerfCounter::ChainingEntriesCount, 1 }, {HashTablePerfCounter::TotalValueSize, expectedTotalValueSize},
{ HashTablePerfCounter::TotalKeySize, expectedTotalKeySize }, {HashTablePerfCounter::TotalIndexSize,
{ HashTablePerfCounter::TotalValueSize, expectedTotalValueSize }, initialTotalIndexSize + sizeof(HashTable::Entry) +
{ (c_dataSetSize * recordOverhead)}});
HashTablePerfCounter::TotalIndexSize,
initialTotalIndexSize + sizeof(HashTable::Entry) + (c_dataSetSize * recordOverhead)
}
});
} }
BOOST_AUTO_TEST_CASE(AddRemoveSameKeyTest) {
BOOST_AUTO_TEST_CASE(AddRemoveSameKeyTest) HashTable hashTable{HashTable::Setting{100, 5}, m_allocator};
{
HashTable hashTable{ HashTable::Setting{ 100, 5 }, m_allocator };
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager); WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable); ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
@ -459,13 +422,15 @@ BOOST_AUTO_TEST_CASE(AddRemoveSameKeyTest)
const auto& perfData = writableHashTable.GetPerfData(); const auto& perfData = writableHashTable.GetPerfData();
// Now remove the first record with key = "key1", which is at the head of the chain. // Now remove the first record with key = "key1", which is at the head of the
// chain.
BOOST_CHECK(writableHashTable.Remove(key1)); BOOST_CHECK(writableHashTable.Remove(key1));
BOOST_CHECK(!readOnlyHashTable.Get(key1, valueRetrieved)); BOOST_CHECK(!readOnlyHashTable.Get(key1, valueRetrieved));
Utils::ValidateCounter(perfData, HashTablePerfCounter::RecordsCount, 1); Utils::ValidateCounter(perfData, HashTablePerfCounter::RecordsCount, 1);
// Now try update the record with key = "key2". This should correctly update the existing record // Now try update the record with key = "key2". This should correctly update
// instead of using the empty slot created by removing the record with key = "key1". // the existing record instead of using the empty slot created by removing the
// record with key = "key1".
auto newVal2 = Utils::ConvertFromString<IReadOnlyHashTable::Value>("newVal2"); auto newVal2 = Utils::ConvertFromString<IReadOnlyHashTable::Value>("newVal2");
writableHashTable.Add(key2, newVal2); writableHashTable.Add(key2, newVal2);
@ -480,53 +445,44 @@ BOOST_AUTO_TEST_CASE(AddRemoveSameKeyTest)
Utils::ValidateCounter(perfData, HashTablePerfCounter::RecordsCount, 0); Utils::ValidateCounter(perfData, HashTablePerfCounter::RecordsCount, 0);
} }
BOOST_AUTO_TEST_CASE(FixedKeyValueHashTableTest) {
BOOST_AUTO_TEST_CASE(FixedKeyValueHashTableTest)
{
// Fixed 4 byte keys and 6 byte values. // Fixed 4 byte keys and 6 byte values.
std::vector<HashTable::Setting> settings = std::vector<HashTable::Setting> settings = {
{ HashTable::Setting{100, 200, 4, 0}, HashTable::Setting{100, 200, 0, 6},
HashTable::Setting{ 100, 200, 4, 0 }, HashTable::Setting{100, 200, 4, 6}};
HashTable::Setting{ 100, 200, 0, 6 },
HashTable::Setting{ 100, 200, 4, 6 }
};
for (const auto& setting : settings) for (const auto& setting : settings) {
{ HashTable hashTable{setting, m_allocator};
HashTable hashTable{ setting, m_allocator };
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager); WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable); ReadOnlyHashTable<Allocator> readOnlyHashTable(hashTable);
constexpr std::uint8_t c_numRecords = 10; constexpr std::uint8_t c_numRecords = 10;
for (std::uint8_t i = 0; i < c_numRecords; ++i) for (std::uint8_t i = 0; i < c_numRecords; ++i) {
{
const std::string keyStr = "key" + std::to_string(i); const std::string keyStr = "key" + std::to_string(i);
const std::string valueStr = "value" + std::to_string(i); const std::string valueStr = "value" + std::to_string(i);
writableHashTable.Add( writableHashTable.Add(
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()), Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()),
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valueStr.c_str())); Utils::ConvertFromString<IReadOnlyHashTable::Value>(
valueStr.c_str()));
} }
Utils::ValidateCounters( Utils::ValidateCounters(writableHashTable.GetPerfData(),
writableHashTable.GetPerfData(), {{HashTablePerfCounter::RecordsCount, 10},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 10 }, {HashTablePerfCounter::TotalKeySize, 40},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalValueSize, 60},
{ HashTablePerfCounter::TotalKeySize, 40 }, {HashTablePerfCounter::MinKeySize, 4},
{ HashTablePerfCounter::TotalValueSize, 60 }, {HashTablePerfCounter::MaxKeySize, 4},
{ HashTablePerfCounter::MinKeySize, 4 }, {HashTablePerfCounter::MinValueSize, 6},
{ HashTablePerfCounter::MaxKeySize, 4 }, {HashTablePerfCounter::MaxValueSize, 6}});
{ HashTablePerfCounter::MinValueSize, 6 },
{ HashTablePerfCounter::MaxValueSize, 6 }
});
for (std::uint8_t i = 0; i < c_numRecords; ++i) for (std::uint8_t i = 0; i < c_numRecords; ++i) {
{
const std::string keyStr = "key" + std::to_string(i); const std::string keyStr = "key" + std::to_string(i);
const std::string valueStr = "value" + std::to_string(i); const std::string valueStr = "value" + std::to_string(i);
const auto expectedValue = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valueStr.c_str()); const auto expectedValue =
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valueStr.c_str());
IReadOnlyHashTable::Value actualValue; IReadOnlyHashTable::Value actualValue;
BOOST_CHECK(readOnlyHashTable.Get( BOOST_CHECK(readOnlyHashTable.Get(
@ -535,30 +491,24 @@ BOOST_AUTO_TEST_CASE(FixedKeyValueHashTableTest)
BOOST_CHECK(expectedValue == actualValue); BOOST_CHECK(expectedValue == actualValue);
} }
for (std::uint8_t i = 0; i < c_numRecords; ++i) for (std::uint8_t i = 0; i < c_numRecords; ++i) {
{
const std::string keyStr = "key" + std::to_string(i); const std::string keyStr = "key" + std::to_string(i);
writableHashTable.Remove( writableHashTable.Remove(
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str())); Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()));
} }
Utils::ValidateCounters( Utils::ValidateCounters(writableHashTable.GetPerfData(),
writableHashTable.GetPerfData(), {{HashTablePerfCounter::RecordsCount, 0},
{ {HashTablePerfCounter::BucketsCount, 100},
{ HashTablePerfCounter::RecordsCount, 0 }, {HashTablePerfCounter::TotalKeySize, 0},
{ HashTablePerfCounter::BucketsCount, 100 }, {HashTablePerfCounter::TotalValueSize, 0}});
{ HashTablePerfCounter::TotalKeySize, 0 },
{ HashTablePerfCounter::TotalValueSize, 0 }
});
} }
} }
BOOST_AUTO_TEST_CASE(HashTableIteratorTest) {
BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
{
Allocator allocator; Allocator allocator;
constexpr std::uint32_t c_numBuckets = 10; constexpr std::uint32_t c_numBuckets = 10;
HashTable hashTable{ HashTable::Setting{ c_numBuckets }, allocator }; HashTable hashTable{HashTable::Setting{c_numBuckets}, allocator};
WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager); WritableHashTable<Allocator> writableHashTable(hashTable, m_epochManager);
{ {
@ -567,12 +517,10 @@ BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
BOOST_CHECK(!iter->MoveNext()); BOOST_CHECK(!iter->MoveNext());
CHECK_EXCEPTION_THROWN_WITH_MESSAGE( CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
iter->GetKey(), iter->GetKey(), "HashTableIterator is not correctly used.");
"HashTableIterator is not correctly used.");
CHECK_EXCEPTION_THROWN_WITH_MESSAGE( CHECK_EXCEPTION_THROWN_WITH_MESSAGE(
iter->GetValue(), iter->GetValue(), "HashTableIterator is not correctly used.");
"HashTableIterator is not correctly used.");
} }
using Buffer = std::vector<std::uint8_t>; using Buffer = std::vector<std::uint8_t>;
@ -581,11 +529,12 @@ BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
BufferMap keyValueMap; BufferMap keyValueMap;
// The number of records should be such that it will create chained entries // The number of records should be such that it will create chained entries
// for at least one bucket. So it should be greater than HashTable::Entry::c_numDataPerEntry * number of buckets. // for at least one bucket. So it should be greater than
constexpr std::uint32_t c_numRecords = (HashTable::Entry::c_numDataPerEntry * c_numBuckets) + 1; // HashTable::Entry::c_numDataPerEntry * number of buckets.
constexpr std::uint32_t c_numRecords =
(HashTable::Entry::c_numDataPerEntry * c_numBuckets) + 1;
for (auto i = 0U; i < c_numRecords; ++i) for (auto i = 0U; i < c_numRecords; ++i) {
{
std::stringstream keyStream; std::stringstream keyStream;
keyStream << "key" << i; keyStream << "key" << i;
@ -595,15 +544,19 @@ BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
std::string keyStr = keyStream.str(); std::string keyStr = keyStream.str();
std::string valStr = valStream.str(); std::string valStr = valStream.str();
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
auto val = Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str()); Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
auto val =
Utils::ConvertFromString<IReadOnlyHashTable::Value>(valStr.c_str());
writableHashTable.Add(key, val); writableHashTable.Add(key, val);
keyValueMap[Buffer(key.m_data, key.m_data + key.m_size)] = Buffer(val.m_data, val.m_data + val.m_size); keyValueMap[Buffer(key.m_data, key.m_data + key.m_size)] =
Buffer(val.m_data, val.m_data + val.m_size);
} }
BOOST_REQUIRE(writableHashTable.GetPerfData().Get(HashTablePerfCounter::MaxBucketChainLength) >= 2); BOOST_REQUIRE(writableHashTable.GetPerfData().Get(
HashTablePerfCounter::MaxBucketChainLength) >= 2);
BOOST_CHECK_EQUAL(keyValueMap.size(), c_numRecords); BOOST_CHECK_EQUAL(keyValueMap.size(), c_numRecords);
{ {
@ -611,37 +564,35 @@ BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
// Validate the data using the iterator. // Validate the data using the iterator.
auto iter = writableHashTable.GetIterator(); auto iter = writableHashTable.GetIterator();
for (auto i = 0U; i < c_numRecords; ++i) for (auto i = 0U; i < c_numRecords; ++i) {
{
BOOST_CHECK(iter->MoveNext()); BOOST_CHECK(iter->MoveNext());
const auto& key = iter->GetKey(); const auto& key = iter->GetKey();
const auto& val = iter->GetValue(); const auto& val = iter->GetValue();
keyValueMapFromIterator[Buffer(key.m_data, key.m_data + key.m_size)] = Buffer(val.m_data, val.m_data + val.m_size); keyValueMapFromIterator[Buffer(key.m_data, key.m_data + key.m_size)] =
Buffer(val.m_data, val.m_data + val.m_size);
} }
BOOST_CHECK(!iter->MoveNext()); BOOST_CHECK(!iter->MoveNext());
BOOST_CHECK(keyValueMap == keyValueMapFromIterator); BOOST_CHECK(keyValueMap == keyValueMapFromIterator);
// Reset should move the iterator to the beginning. // Reset should move the iterator to the beginning.
iter->Reset(); iter->Reset();
for (auto i = 0U; i < c_numRecords; ++i) for (auto i = 0U; i < c_numRecords; ++i) {
{
BOOST_CHECK(iter->MoveNext()); BOOST_CHECK(iter->MoveNext());
} }
BOOST_CHECK(!iter->MoveNext()); BOOST_CHECK(!iter->MoveNext());
} }
// Remove half of the key. // Remove half of the key.
for (auto i = 0U; i < c_numRecords; ++i) for (auto i = 0U; i < c_numRecords; ++i) {
{ if (i % 2 == 0U) {
if (i % 2 == 0U)
{
std::stringstream keyStream; std::stringstream keyStream;
keyStream << "key" << i; keyStream << "key" << i;
std::string keyStr = keyStream.str(); std::string keyStr = keyStream.str();
auto key = Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str()); auto key =
Utils::ConvertFromString<IReadOnlyHashTable::Key>(keyStr.c_str());
BOOST_CHECK(writableHashTable.Remove(key)); BOOST_CHECK(writableHashTable.Remove(key));
@ -655,8 +606,7 @@ BOOST_AUTO_TEST_CASE(HashTableIteratorTest)
{ {
BufferMap keyValueMapFromIterator; BufferMap keyValueMapFromIterator;
auto iter = writableHashTable.GetIterator(); auto iter = writableHashTable.GetIterator();
for (auto i = 0U; i < c_numRecords / 2U; ++i) for (auto i = 0U; i < c_numRecords / 2U; ++i) {
{
BOOST_CHECK(iter->MoveNext()); BOOST_CHECK(iter->MoveNext());
const auto& key = iter->GetKey(); const auto& key = iter->GetKey();

Просмотреть файл

@ -1,20 +1,18 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include "L4/HashTable/Common/SettingAdapter.h"
#include "L4/HashTable/Common/Record.h"
#include "CheckedAllocator.h" #include "CheckedAllocator.h"
#include "L4/HashTable/Common/Record.h"
#include "L4/HashTable/Common/SettingAdapter.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
using SharedHashTable = HashTable::SharedHashTable<HashTable::RecordBuffer, CheckedAllocator<>>; using SharedHashTable =
HashTable::SharedHashTable<HashTable::RecordBuffer, CheckedAllocator<>>;
BOOST_AUTO_TEST_SUITE(SettingAdapterTests) BOOST_AUTO_TEST_SUITE(SettingAdapterTests)
BOOST_AUTO_TEST_CASE(SettingAdapterTestWithDefaultValues) BOOST_AUTO_TEST_CASE(SettingAdapterTestWithDefaultValues) {
{ HashTableConfig::Setting from{100U};
HashTableConfig::Setting from{ 100U };
const auto to = HashTable::SettingAdapter{}.Convert<SharedHashTable>(from); const auto to = HashTable::SettingAdapter{}.Convert<SharedHashTable>(from);
BOOST_CHECK_EQUAL(to.m_numBuckets, 100U); BOOST_CHECK_EQUAL(to.m_numBuckets, 100U);
@ -23,10 +21,8 @@ BOOST_AUTO_TEST_CASE(SettingAdapterTestWithDefaultValues)
BOOST_CHECK_EQUAL(to.m_fixedValueSize, 0U); BOOST_CHECK_EQUAL(to.m_fixedValueSize, 0U);
} }
BOOST_AUTO_TEST_CASE(SettingAdapterTestWithNonDefaultValues) {
BOOST_AUTO_TEST_CASE(SettingAdapterTestWithNonDefaultValues) HashTableConfig::Setting from{100U, 10U, 5U, 20U};
{
HashTableConfig::Setting from{ 100U, 10U, 5U, 20U };
const auto to = HashTable::SettingAdapter{}.Convert<SharedHashTable>(from); const auto to = HashTable::SettingAdapter{}.Convert<SharedHashTable>(from);
BOOST_CHECK_EQUAL(to.m_numBuckets, 100U); BOOST_CHECK_EQUAL(to.m_numBuckets, 100U);

Просмотреть файл

@ -1,33 +1,23 @@
#include <boost/test/unit_test.hpp>
#include "Utils.h" #include "Utils.h"
#include <boost/test/unit_test.hpp>
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests namespace Utils {
{
namespace Utils
{
void ValidateCounter(const HashTablePerfData& actual,
void ValidateCounter(
const HashTablePerfData& actual,
HashTablePerfCounter perfCounter, HashTablePerfCounter perfCounter,
PerfCounters<HashTablePerfCounter>::TValue expectedValue) PerfCounters<HashTablePerfCounter>::TValue expectedValue) {
{
BOOST_CHECK_MESSAGE( BOOST_CHECK_MESSAGE(
actual.Get(perfCounter) == expectedValue, actual.Get(perfCounter) == expectedValue,
c_hashTablePerfCounterNames[static_cast<std::size_t>(perfCounter)] c_hashTablePerfCounterNames[static_cast<std::size_t>(perfCounter)]
<< " counter: " << " counter: " << actual.Get(perfCounter)
<< actual.Get(perfCounter)
<< " (actual) != " << expectedValue << " (expected)."); << " (actual) != " << expectedValue << " (expected).");
} }
void ValidateCounters( void ValidateCounters(const HashTablePerfData& actual,
const HashTablePerfData& actual, const ExpectedCounterValues& expected) {
const ExpectedCounterValues& expected) for (const auto& expectedCounter : expected) {
{
for (const auto& expectedCounter : expected)
{
ValidateCounter(actual, expectedCounter.first, expectedCounter.second); ValidateCounter(actual, expectedCounter.first, expectedCounter.second);
} }
} }

Просмотреть файл

@ -1,109 +1,86 @@
#pragma once #pragma once
#include <string.h>
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <string.h>
#include <vector> #include <vector>
#include "L4/Log/PerfCounter.h" #include "L4/Log/PerfCounter.h"
#include "L4/Utils/Exception.h" #include "L4/Utils/Exception.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
// Macro CHECK_EXCEPTION_THROWN // Macro CHECK_EXCEPTION_THROWN
#define CHECK_EXCEPTION_THROWN(statement) \ #define CHECK_EXCEPTION_THROWN(statement) \
do { \ do { \
bool isExceptionThrown = false;\ bool isExceptionThrown = false; \
try \ try { \
{ \
statement; \ statement; \
} \ } catch (const RuntimeException&) { \
catch (const RuntimeException&) \
{ \
isExceptionThrown = true; \ isExceptionThrown = true; \
} \ } \
BOOST_CHECK(isExceptionThrown); \ BOOST_CHECK(isExceptionThrown); \
} while (0) } while (0)
#define CHECK_EXCEPTION_THROWN_WITH_MESSAGE(statement, message) \ #define CHECK_EXCEPTION_THROWN_WITH_MESSAGE(statement, message) \
do { \ do { \
bool isExceptionThrown = false; \ bool isExceptionThrown = false; \
std::string exceptionMsg; \ std::string exceptionMsg; \
try \ try { \
{ \
statement; \ statement; \
} \ } catch (const RuntimeException& ex) { \
catch (const RuntimeException& ex) \
{ \
isExceptionThrown = true; \ isExceptionThrown = true; \
exceptionMsg = ex.what(); \ exceptionMsg = ex.what(); \
BOOST_TEST_MESSAGE("Exception Message: " << exceptionMsg); \ BOOST_TEST_MESSAGE("Exception Message: " << exceptionMsg); \
} \ } \
BOOST_CHECK(isExceptionThrown); \ BOOST_CHECK(isExceptionThrown); \
BOOST_CHECK(strcmp((message), exceptionMsg.c_str()) == 0); \ BOOST_CHECK(strcmp((message), exceptionMsg.c_str()) == 0); \
} while (0) } while (0)
// This will validate the given message is a prefix of the exception message. // This will validate the given message is a prefix of the exception message.
#define CHECK_EXCEPTION_THROWN_WITH_PREFIX_MESSAGE(statement, message) \ #define CHECK_EXCEPTION_THROWN_WITH_PREFIX_MESSAGE(statement, message) \
do { \ do { \
bool isExceptionThrown = false; \ bool isExceptionThrown = false; \
std::string exceptionMsg; \ std::string exceptionMsg; \
try \ try { \
{ \
statement; \ statement; \
} \ } catch (const RuntimeException& ex) { \
catch (const RuntimeException& ex) \
{ \
isExceptionThrown = true; \ isExceptionThrown = true; \
exceptionMsg = ex.what(); \ exceptionMsg = ex.what(); \
BOOST_TEST_MESSAGE("Exception Message: " << exceptionMsg); \ BOOST_TEST_MESSAGE("Exception Message: " << exceptionMsg); \
} \ } \
BOOST_CHECK(isExceptionThrown); \ BOOST_CHECK(isExceptionThrown); \
BOOST_CHECK(exceptionMsg.compare(0, strlen(message), message) == 0); \ BOOST_CHECK(exceptionMsg.compare(0, strlen(message), message) == 0); \
} while (0) } while (0)
namespace Utils {
namespace Utils
{
template <typename T> template <typename T>
T ConvertFromString(const char* str) T ConvertFromString(const char* str) {
{ return T(reinterpret_cast<const std::uint8_t*>(str),
return T(
reinterpret_cast<const std::uint8_t*>(str),
static_cast<typename T::size_type>(strlen(str))); static_cast<typename T::size_type>(strlen(str)));
} }
template <typename T> template <typename T>
std::string ConvertToString(const T& t) std::string ConvertToString(const T& t) {
{
return std::string(reinterpret_cast<const char*>(t.m_data), t.m_size); return std::string(reinterpret_cast<const char*>(t.m_data), t.m_size);
} }
// Counter related validation util function. // Counter related validation util function.
using ExpectedCounterValues using ExpectedCounterValues =
= std::vector< std::vector<std::pair<HashTablePerfCounter,
std::pair<
HashTablePerfCounter,
typename PerfCounters<HashTablePerfCounter>::TValue>>; typename PerfCounters<HashTablePerfCounter>::TValue>>;
// Validate the given perfData against the expected counter value. // Validate the given perfData against the expected counter value.
void ValidateCounter( void ValidateCounter(const HashTablePerfData& actual,
const HashTablePerfData& actual,
HashTablePerfCounter perfCounter, HashTablePerfCounter perfCounter,
PerfCounters<HashTablePerfCounter>::TValue expectedValue); PerfCounters<HashTablePerfCounter>::TValue expectedValue);
// Validate the given perfData against the expected counter values. // Validate the given perfData against the expected counter values.
void ValidateCounters( void ValidateCounters(const HashTablePerfData& actual,
const HashTablePerfData& actual,
const ExpectedCounterValues& expected); const ExpectedCounterValues& expected);
} // namespace Utils } // namespace Utils

Просмотреть файл

@ -1,16 +1,13 @@
#include <boost/test/unit_test.hpp>
#include <array> #include <array>
#include <boost/test/unit_test.hpp>
#include "L4/Utils/Math.h" #include "L4/Utils/Math.h"
namespace L4 namespace L4 {
{ namespace UnitTests {
namespace UnitTests
{
using namespace Utils; using namespace Utils;
BOOST_AUTO_TEST_CASE(MathTest) BOOST_AUTO_TEST_CASE(MathTest) {
{
// RoundUp tests. // RoundUp tests.
BOOST_CHECK_EQUAL(Math::RoundUp(5, 10), 10); BOOST_CHECK_EQUAL(Math::RoundUp(5, 10), 10);
BOOST_CHECK_EQUAL(Math::RoundUp(10, 10), 10); BOOST_CHECK_EQUAL(Math::RoundUp(10, 10), 10);
@ -39,15 +36,17 @@ BOOST_AUTO_TEST_CASE(MathTest)
BOOST_CHECK_EQUAL(Math::NextHighestPowerOfTwo(200), 256U); BOOST_CHECK_EQUAL(Math::NextHighestPowerOfTwo(200), 256U);
} }
BOOST_AUTO_TEST_CASE(PointerArithmeticTest) {
BOOST_AUTO_TEST_CASE(PointerArithmeticTest)
{
std::array<int, 3> elements; std::array<int, 3> elements;
BOOST_CHECK(reinterpret_cast<int*>(Math::PointerArithmetic::Add(&elements[0], sizeof(int))) == &elements[1]); BOOST_CHECK(reinterpret_cast<int*>(Math::PointerArithmetic::Add(
BOOST_CHECK(reinterpret_cast<int*>(Math::PointerArithmetic::Subtract(&elements[1], sizeof(int))) == &elements[0]); &elements[0], sizeof(int))) == &elements[1]);
BOOST_CHECK(Math::PointerArithmetic::Distance(&elements[2], &elements[0]) == sizeof(int) * 2U); BOOST_CHECK(reinterpret_cast<int*>(Math::PointerArithmetic::Subtract(
BOOST_CHECK(Math::PointerArithmetic::Distance(&elements[0], &elements[2]) == sizeof(int) * 2U); &elements[1], sizeof(int))) == &elements[0]);
BOOST_CHECK(Math::PointerArithmetic::Distance(&elements[2], &elements[0]) ==
sizeof(int) * 2U);
BOOST_CHECK(Math::PointerArithmetic::Distance(&elements[0], &elements[2]) ==
sizeof(int) * 2U);
} }
} // namespace UnitTests } // namespace UnitTests

Просмотреть файл

@ -1,28 +1,26 @@
#pragma once #pragma once
#include <cstdint>
#include <chrono> #include <chrono>
#include <cstdint>
namespace L4 namespace L4 {
{
// EpochManagerConfig struct. // EpochManagerConfig struct.
struct EpochManagerConfig struct EpochManagerConfig {
{ // "numActionQueues" indicates how many action containers there will be in
// "numActionQueues" indicates how many action containers there will be in order to // order to increase the throughput of registering an action.
// increase the throughput of registering an action. // "performActionsInParallelThreshold" indicates the threshold value above
// "performActionsInParallelThreshold" indicates the threshold value above which // which the actions are performed in parallel.
// the actions are performed in parallel. // "maxNumThreadsToPerformActions" indicates how many threads will be used
// "maxNumThreadsToPerformActions" indicates how many threads will be used when // when performing an action in parallel.
// performing an action in parallel.
explicit EpochManagerConfig( explicit EpochManagerConfig(
std::uint32_t epochQueueSize = 1000, std::uint32_t epochQueueSize = 1000,
std::chrono::milliseconds epochProcessingInterval = std::chrono::milliseconds{ 1000 }, std::chrono::milliseconds epochProcessingInterval =
std::chrono::milliseconds{1000},
std::uint8_t numActionQueues = 1) std::uint8_t numActionQueues = 1)
: m_epochQueueSize{ epochQueueSize } : m_epochQueueSize{epochQueueSize},
, m_epochProcessingInterval{ epochProcessingInterval } m_epochProcessingInterval{epochProcessingInterval},
, m_numActionQueues{ numActionQueues } m_numActionQueues{numActionQueues} {}
{}
std::uint32_t m_epochQueueSize; std::uint32_t m_epochQueueSize;
std::chrono::milliseconds m_epochProcessingInterval; std::chrono::milliseconds m_epochProcessingInterval;

Просмотреть файл

@ -11,24 +11,22 @@
#include "IEpochActionManager.h" #include "IEpochActionManager.h"
#include "Utils/Lock.h" #include "Utils/Lock.h"
namespace L4 namespace L4 {
{
// EpochActionManager provides functionalities to add actions at an epoch and to
// EpochActionManager provides functionalities to add actions at an epoch and to perform // perform actions up to the given epoch.
// actions up to the given epoch. class EpochActionManager {
class EpochActionManager public:
{ // "numActionQueues" indicates how many action containers there will be in
public: // order to increase the throughput of registering an action. This will be
// "numActionQueues" indicates how many action containers there will be in order to // re-calculated to the next highest power of two so that the "&" operator can
// increase the throughput of registering an action. This will be re-calculated to // be used for accessing the next queue.
// the next highest power of two so that the "&" operator can be used for accessing
// the next queue.
explicit EpochActionManager(std::uint8_t numActionQueues); explicit EpochActionManager(std::uint8_t numActionQueues);
// Adds an action at a given epoch counter. // Adds an action at a given epoch counter.
// This function is thread-safe. // This function is thread-safe.
void RegisterAction(std::uint64_t epochCounter, IEpochActionManager::Action&& action); void RegisterAction(std::uint64_t epochCounter,
IEpochActionManager::Action&& action);
// Perform actions whose associated epoch counter value is less than // Perform actions whose associated epoch counter value is less than
// the given epoch counter value, and returns the number of actions performed. // the given epoch counter value, and returns the number of actions performed.
@ -37,7 +35,7 @@ public:
EpochActionManager(const EpochActionManager&) = delete; EpochActionManager(const EpochActionManager&) = delete;
EpochActionManager& operator=(const EpochActionManager&) = delete; EpochActionManager& operator=(const EpochActionManager&) = delete;
private: private:
using Mutex = Utils::CriticalSection; using Mutex = Utils::CriticalSection;
using Lock = std::lock_guard<Mutex>; using Lock = std::lock_guard<Mutex>;
@ -47,7 +45,8 @@ private:
// If the performance of using std::map becomes an issue, we can revisit this. // If the performance of using std::map becomes an issue, we can revisit this.
using EpochToActions = std::map<std::uint64_t, Actions>; using EpochToActions = std::map<std::uint64_t, Actions>;
using EpochToActionsWithLock = std::tuple<std::unique_ptr<Mutex>, EpochToActions>; using EpochToActionsWithLock =
std::tuple<std::unique_ptr<Mutex>, EpochToActions>;
// Run actions based on the configuration. // Run actions based on the configuration.
void ApplyActions(Actions& actions); void ApplyActions(Actions& actions);
@ -59,5 +58,4 @@ private:
std::atomic<std::uint32_t> m_counter; std::atomic<std::uint32_t> m_counter;
}; };
} // namespace L4 } // namespace L4

Просмотреть файл

@ -7,35 +7,30 @@
#include "Utils/Exception.h" #include "Utils/Exception.h"
#include "Utils/Lock.h" #include "Utils/Lock.h"
namespace L4 namespace L4 {
{
// EpochQueue struct represents reference counts for each epoch. // EpochQueue struct represents reference counts for each epoch.
// Each value of the queue (fixed-size array) is the reference counts at an index, // Each value of the queue (fixed-size array) is the reference counts at an
// where an index represents an epoch (time). // index, where an index represents an epoch (time).
template < template <typename TSharableLock,
typename TSharableLock,
typename TExclusiveLock, typename TExclusiveLock,
typename Allocator = std::allocator<void> typename Allocator = std::allocator<void> >
> struct EpochQueue {
struct EpochQueue static_assert(std::is_same<typename TSharableLock::mutex_type,
{ typename TExclusiveLock::mutex_type>::value,
static_assert(
std::is_same<typename TSharableLock::mutex_type, typename TExclusiveLock::mutex_type>::value,
"mutex type should be the same"); "mutex type should be the same");
public: public:
EpochQueue( EpochQueue(std::uint64_t epochCounter,
std::uint64_t epochCounter,
std::uint32_t queueSize, std::uint32_t queueSize,
Allocator allocator = Allocator()) Allocator allocator = Allocator())
: m_frontIndex{ epochCounter } : m_frontIndex{epochCounter},
, m_backIndex{ epochCounter } m_backIndex{epochCounter},
, m_mutexForBackIndex{} m_mutexForBackIndex{},
, m_refCounts{ queueSize, typename Allocator::template rebind<RefCount>::other(allocator) } m_refCounts{
{ queueSize,
if (queueSize == 0U) typename Allocator::template rebind<RefCount>::other(allocator)} {
{ if (queueSize == 0U) {
throw RuntimeException("Zero queue size is not allowed."); throw RuntimeException("Zero queue size is not allowed.");
} }
} }
@ -43,9 +38,8 @@ public:
using SharableLock = TSharableLock; using SharableLock = TSharableLock;
using ExclusiveLock = TExclusiveLock; using ExclusiveLock = TExclusiveLock;
using RefCount = std::atomic<std::uint32_t>; using RefCount = std::atomic<std::uint32_t>;
using RefCounts = Interprocess::Container::Vector< using RefCounts = Interprocess::Container::
RefCount, Vector<RefCount, typename Allocator::template rebind<RefCount>::other>;
typename Allocator::template rebind<RefCount>::other>;
// The followings (m_frontIndex and m_backIndex) are // The followings (m_frontIndex and m_backIndex) are
// accessed/updated only by the owner thread (only one thread), thus // accessed/updated only by the owner thread (only one thread), thus
@ -61,42 +55,38 @@ public:
typename SharableLock::mutex_type m_mutexForBackIndex; typename SharableLock::mutex_type m_mutexForBackIndex;
// Reference counts per epoch count. // Reference counts per epoch count.
// The index represents the epoch counter value and the value represents the reference counts. // The index represents the epoch counter value and the value represents the
// reference counts.
RefCounts m_refCounts; RefCounts m_refCounts;
}; };
// EpochRefManager provides functionality of adding/removing references // EpochRefManager provides functionality of adding/removing references
// to the epoch counter. // to the epoch counter.
template <typename EpochQueue> template <typename EpochQueue>
class EpochRefManager class EpochRefManager {
{ public:
public: explicit EpochRefManager(EpochQueue& epochQueue) : m_epochQueue(epochQueue) {}
explicit EpochRefManager(EpochQueue& epochQueue)
: m_epochQueue(epochQueue)
{}
// Increment a reference to the current epoch counter. // Increment a reference to the current epoch counter.
// This function is thread-safe. // This function is thread-safe.
std::uint64_t AddRef() std::uint64_t AddRef() {
{
// The synchronization is needed for EpochCounterManager::AddNewEpoch(). // The synchronization is needed for EpochCounterManager::AddNewEpoch().
typename EpochQueue::SharableLock lock(m_epochQueue.m_mutexForBackIndex); typename EpochQueue::SharableLock lock(m_epochQueue.m_mutexForBackIndex);
++m_epochQueue.m_refCounts[m_epochQueue.m_backIndex % m_epochQueue.m_refCounts.size()]; ++m_epochQueue.m_refCounts[m_epochQueue.m_backIndex %
m_epochQueue.m_refCounts.size()];
return m_epochQueue.m_backIndex; return m_epochQueue.m_backIndex;
} }
// Decrement a reference count for the given epoch counter. // Decrement a reference count for the given epoch counter.
// This function is thread-safe. // This function is thread-safe.
void RemoveRef(std::uint64_t epochCounter) void RemoveRef(std::uint64_t epochCounter) {
{ auto& refCounter =
auto& refCounter = m_epochQueue.m_refCounts[epochCounter % m_epochQueue.m_refCounts.size()]; m_epochQueue
.m_refCounts[epochCounter % m_epochQueue.m_refCounts.size()];
if (refCounter == 0) if (refCounter == 0) {
{
throw RuntimeException("Reference counter is invalid."); throw RuntimeException("Reference counter is invalid.");
} }
@ -106,25 +96,21 @@ public:
EpochRefManager(const EpochRefManager&) = delete; EpochRefManager(const EpochRefManager&) = delete;
EpochRefManager& operator=(const EpochRefManager&) = delete; EpochRefManager& operator=(const EpochRefManager&) = delete;
private: private:
EpochQueue& m_epochQueue; EpochQueue& m_epochQueue;
}; };
// EpochCounterManager provides functionality of updating the current epoch
// EpochCounterManager provides functionality of updating the current epoch counter // counter and getting the latest unreferenced epoch counter.
// and getting the latest unreferenced epoch counter.
template <typename EpochQueue> template <typename EpochQueue>
class EpochCounterManager class EpochCounterManager {
{ public:
public:
explicit EpochCounterManager(EpochQueue& epochQueue) explicit EpochCounterManager(EpochQueue& epochQueue)
: m_epochQueue(epochQueue) : m_epochQueue(epochQueue) {}
{}
// Increments the current epoch count by one. // Increments the current epoch count by one.
// This function is thread-safe. // This function is thread-safe.
void AddNewEpoch() void AddNewEpoch() {
{
// The synchronization is needed for EpochRefManager::AddRef(). // The synchronization is needed for EpochRefManager::AddRef().
typename EpochQueue::ExclusiveLock lock(m_epochQueue.m_mutexForBackIndex); typename EpochQueue::ExclusiveLock lock(m_epochQueue.m_mutexForBackIndex);
@ -137,17 +123,14 @@ public:
// count such that all other epoch counts' references are zeros. // count such that all other epoch counts' references are zeros.
// Note that this function is NOT thread safe, and should be run on the // Note that this function is NOT thread safe, and should be run on the
// same thread as the one that calls AddNewEpoch(). // same thread as the one that calls AddNewEpoch().
std::uint64_t RemoveUnreferenceEpochCounters() std::uint64_t RemoveUnreferenceEpochCounters() {
{ while (m_epochQueue.m_backIndex > m_epochQueue.m_frontIndex) {
while (m_epochQueue.m_backIndex > m_epochQueue.m_frontIndex) if (m_epochQueue.m_refCounts[m_epochQueue.m_frontIndex %
{ m_epochQueue.m_refCounts.size()] == 0U) {
if (m_epochQueue.m_refCounts[m_epochQueue.m_frontIndex % m_epochQueue.m_refCounts.size()] == 0U)
{
++m_epochQueue.m_frontIndex; ++m_epochQueue.m_frontIndex;
} } else {
else // There are references to the front of the queue and will return this
{ // front index.
// There are references to the front of the queue and will return this front index.
break; break;
} }
} }
@ -158,7 +141,7 @@ public:
EpochCounterManager(const EpochCounterManager&) = delete; EpochCounterManager(const EpochCounterManager&) = delete;
EpochCounterManager& operator=(const EpochCounterManager&) = delete; EpochCounterManager& operator=(const EpochCounterManager&) = delete;
private: private:
EpochQueue& m_epochQueue; EpochQueue& m_epochQueue;
}; };

Просмотреть файл

@ -1,32 +1,27 @@
#pragma once #pragma once
#include <cstdint>
#include <boost/integer_traits.hpp> #include <boost/integer_traits.hpp>
#include <cstdint>
namespace L4 namespace L4 {
{
// EpochRefPolicy class // EpochRefPolicy class
template <typename EpochRefManager> template <typename EpochRefManager>
class EpochRefPolicy class EpochRefPolicy {
{ public:
public:
explicit EpochRefPolicy(EpochRefManager& epochRefManager) explicit EpochRefPolicy(EpochRefManager& epochRefManager)
: m_epochRefManager{ epochRefManager } : m_epochRefManager{epochRefManager},
, m_epochCounter{ m_epochRefManager.AddRef() } m_epochCounter{m_epochRefManager.AddRef()} {}
{}
EpochRefPolicy(EpochRefPolicy&& epochRefPolicy) EpochRefPolicy(EpochRefPolicy&& epochRefPolicy)
: m_epochRefManager{ epochRefPolicy.m_epochRefManager } : m_epochRefManager{epochRefPolicy.m_epochRefManager},
, m_epochCounter{ epochRefPolicy.m_epochCounter } m_epochCounter{epochRefPolicy.m_epochCounter} {
{ epochRefPolicy.m_epochCounter =
epochRefPolicy.m_epochCounter = boost::integer_traits<std::uint64_t>::const_max; boost::integer_traits<std::uint64_t>::const_max;
} }
~EpochRefPolicy() ~EpochRefPolicy() {
{ if (m_epochCounter != boost::integer_traits<std::uint64_t>::const_max) {
if (m_epochCounter != boost::integer_traits<std::uint64_t>::const_max)
{
m_epochRefManager.RemoveRef(m_epochCounter); m_epochRefManager.RemoveRef(m_epochCounter);
} }
} }
@ -34,7 +29,7 @@ public:
EpochRefPolicy(const EpochRefPolicy&) = delete; EpochRefPolicy(const EpochRefPolicy&) = delete;
EpochRefPolicy& operator=(const EpochRefPolicy&) = delete; EpochRefPolicy& operator=(const EpochRefPolicy&) = delete;
private: private:
EpochRefManager& m_epochRefManager; EpochRefManager& m_epochRefManager;
std::uint64_t m_epochCounter; std::uint64_t m_epochCounter;
}; };

Просмотреть файл

@ -2,21 +2,17 @@
#include <functional> #include <functional>
namespace L4 namespace L4 {
{
// IEpochActionManager interface exposes an API for registering an Action. // IEpochActionManager interface exposes an API for registering an Action.
struct IEpochActionManager struct IEpochActionManager {
{
using Action = std::function<void()>; using Action = std::function<void()>;
virtual ~IEpochActionManager() {}; virtual ~IEpochActionManager(){};
// Register actions on the latest epoch in the queue and the action is // Register actions on the latest epoch in the queue and the action is
// performed when the epoch is removed from the queue. // performed when the epoch is removed from the queue.
virtual void RegisterAction(Action&& action) = 0; virtual void RegisterAction(Action&& action) = 0;
}; };
} // namespace L4 } // namespace L4

Просмотреть файл

@ -4,28 +4,24 @@
#include <cstdint> #include <cstdint>
#include <mutex> #include <mutex>
#include <stdexcept> #include <stdexcept>
#include "detail/ToRawPointer.h"
#include "Epoch/IEpochActionManager.h" #include "Epoch/IEpochActionManager.h"
#include "HashTable/Cache/Metadata.h"
#include "HashTable/IHashTable.h" #include "HashTable/IHashTable.h"
#include "HashTable/ReadWrite/HashTable.h" #include "HashTable/ReadWrite/HashTable.h"
#include "HashTable/Cache/Metadata.h"
#include "Utils/Clock.h" #include "Utils/Clock.h"
#include "detail/ToRawPointer.h"
namespace L4 namespace L4 {
{ namespace HashTable {
namespace HashTable namespace Cache {
{
namespace Cache
{
// ReadOnlyHashTable class implements IReadOnlyHashTable interface and provides // ReadOnlyHashTable class implements IReadOnlyHashTable interface and provides
// the functionality to read data given a key. // the functionality to read data given a key.
template <typename Allocator, typename Clock = Utils::EpochClock> template <typename Allocator, typename Clock = Utils::EpochClock>
class ReadOnlyHashTable class ReadOnlyHashTable
: public virtual ReadWrite::ReadOnlyHashTable<Allocator> : public virtual ReadWrite::ReadOnlyHashTable<Allocator>,
, protected Clock protected Clock {
{ public:
public:
using Base = ReadWrite::ReadOnlyHashTable<Allocator>; using Base = ReadWrite::ReadOnlyHashTable<Allocator>;
using HashTable = typename Base::HashTable; using HashTable = typename Base::HashTable;
@ -35,58 +31,48 @@ public:
class Iterator; class Iterator;
ReadOnlyHashTable( ReadOnlyHashTable(HashTable& hashTable, std::chrono::seconds recordTimeToLive)
HashTable& hashTable, : Base(hashTable,
std::chrono::seconds recordTimeToLive) RecordSerializer{hashTable.m_setting.m_fixedKeySize,
: Base(
hashTable,
RecordSerializer{
hashTable.m_setting.m_fixedKeySize,
hashTable.m_setting.m_fixedValueSize, hashTable.m_setting.m_fixedValueSize,
Metadata::c_metaDataSize }) Metadata::c_metaDataSize}),
, m_recordTimeToLive{ recordTimeToLive } m_recordTimeToLive{recordTimeToLive} {}
{}
virtual bool Get(const Key& key, Value& value) const override virtual bool Get(const Key& key, Value& value) const override {
{
const auto status = GetInternal(key, value); const auto status = GetInternal(key, value);
// Note that the following const_cast is safe and necessary to update cache hit information. // Note that the following const_cast is safe and necessary to update cache
const_cast<HashTablePerfData&>(this->GetPerfData()).Increment( // hit information.
status const_cast<HashTablePerfData&>(this->GetPerfData())
? HashTablePerfCounter::CacheHitCount .Increment(status ? HashTablePerfCounter::CacheHitCount
: HashTablePerfCounter::CacheMissCount); : HashTablePerfCounter::CacheMissCount);
return status; return status;
} }
virtual IIteratorPtr GetIterator() const override virtual IIteratorPtr GetIterator() const override {
{
return std::make_unique<Iterator>( return std::make_unique<Iterator>(
this->m_hashTable, this->m_hashTable, this->m_recordSerializer, m_recordTimeToLive,
this->m_recordSerializer,
m_recordTimeToLive,
this->GetCurrentEpochTime()); this->GetCurrentEpochTime());
} }
ReadOnlyHashTable(const ReadOnlyHashTable&) = delete; ReadOnlyHashTable(const ReadOnlyHashTable&) = delete;
ReadOnlyHashTable& operator=(const ReadOnlyHashTable&) = delete; ReadOnlyHashTable& operator=(const ReadOnlyHashTable&) = delete;
protected: protected:
bool GetInternal(const Key& key, Value& value) const bool GetInternal(const Key& key, Value& value) const {
{ if (!Base::Get(key, value)) {
if (!Base::Get(key, value))
{
return false; return false;
} }
assert(value.m_size > Metadata::c_metaDataSize); assert(value.m_size > Metadata::c_metaDataSize);
// If the record with the given key is found, check if the record is expired or not. // If the record with the given key is found, check if the record is expired
// Note that the following const_cast is safe and necessary to update the access status. // or not. Note that the following const_cast is safe and necessary to
Metadata metaData{ const_cast<std::uint32_t*>(reinterpret_cast<const std::uint32_t*>(value.m_data)) }; // update the access status.
if (metaData.IsExpired(this->GetCurrentEpochTime(), m_recordTimeToLive)) Metadata metaData{const_cast<std::uint32_t*>(
{ reinterpret_cast<const std::uint32_t*>(value.m_data))};
if (metaData.IsExpired(this->GetCurrentEpochTime(), m_recordTimeToLive)) {
return false; return false;
} }
@ -101,45 +87,35 @@ protected:
std::chrono::seconds m_recordTimeToLive; std::chrono::seconds m_recordTimeToLive;
}; };
template <typename Allocator, typename Clock> template <typename Allocator, typename Clock>
class ReadOnlyHashTable<Allocator, Clock>::Iterator : public Base::Iterator class ReadOnlyHashTable<Allocator, Clock>::Iterator : public Base::Iterator {
{ public:
public:
using BaseIterator = typename Base::Iterator; using BaseIterator = typename Base::Iterator;
Iterator( Iterator(const HashTable& hashTable,
const HashTable& hashTable,
const RecordSerializer& recordDeserializer, const RecordSerializer& recordDeserializer,
std::chrono::seconds recordTimeToLive, std::chrono::seconds recordTimeToLive,
std::chrono::seconds currentEpochTime) std::chrono::seconds currentEpochTime)
: BaseIterator(hashTable, recordDeserializer) : BaseIterator(hashTable, recordDeserializer),
, m_recordTimeToLive{ recordTimeToLive } m_recordTimeToLive{recordTimeToLive},
, m_currentEpochTime{ currentEpochTime } m_currentEpochTime{currentEpochTime} {}
{}
Iterator(Iterator&& other) Iterator(Iterator&& other)
: BaseIterator(std::move(other)) : BaseIterator(std::move(other)),
, m_recordTimeToLive{ std::move(other.m_recordTimeToLive) } m_recordTimeToLive{std::move(other.m_recordTimeToLive)},
, m_currentEpochTime{ std::move(other.m_currentEpochTime) } m_currentEpochTime{std::move(other.m_currentEpochTime)} {}
{}
bool MoveNext() override bool MoveNext() override {
{ if (!BaseIterator::MoveNext()) {
if (!BaseIterator::MoveNext())
{
return false; return false;
} }
do do {
{
const Metadata metaData{ const Metadata metaData{
const_cast<std::uint32_t*>( const_cast<std::uint32_t*>(reinterpret_cast<const std::uint32_t*>(
reinterpret_cast<const std::uint32_t*>( BaseIterator::GetValue().m_data))};
BaseIterator::GetValue().m_data)) };
if (!metaData.IsExpired(m_currentEpochTime, m_recordTimeToLive)) if (!metaData.IsExpired(m_currentEpochTime, m_recordTimeToLive)) {
{
return true; return true;
} }
} while (BaseIterator::MoveNext()); } while (BaseIterator::MoveNext());
@ -147,8 +123,7 @@ public:
return false; return false;
} }
Value GetValue() const override Value GetValue() const override {
{
auto value = BaseIterator::GetValue(); auto value = BaseIterator::GetValue();
value.m_data += Metadata::c_metaDataSize; value.m_data += Metadata::c_metaDataSize;
value.m_size -= Metadata::c_metaDataSize; value.m_size -= Metadata::c_metaDataSize;
@ -156,25 +131,22 @@ public:
return value; return value;
} }
private: private:
std::chrono::seconds m_recordTimeToLive; std::chrono::seconds m_recordTimeToLive;
std::chrono::seconds m_currentEpochTime; std::chrono::seconds m_currentEpochTime;
}; };
// The following warning is from the virtual inheritance and safe to disable in
// The following warning is from the virtual inheritance and safe to disable in this case. // this case. https://msdn.microsoft.com/en-us/library/6b3sy7ae.aspx
// https://msdn.microsoft.com/en-us/library/6b3sy7ae.aspx
#pragma warning(push) #pragma warning(push)
#pragma warning(disable:4250) #pragma warning(disable : 4250)
// WritableHashTable class implements IWritableHashTable interface and also provides // WritableHashTable class implements IWritableHashTable interface and also
// the read only access (Get()) to the hash table. // provides the read only access (Get()) to the hash table.
template <typename Allocator, typename Clock = Utils::EpochClock> template <typename Allocator, typename Clock = Utils::EpochClock>
class WritableHashTable class WritableHashTable : public ReadOnlyHashTable<Allocator, Clock>,
: public ReadOnlyHashTable<Allocator, Clock> public ReadWrite::WritableHashTable<Allocator> {
, public ReadWrite::WritableHashTable<Allocator> public:
{
public:
using ReadOnlyBase = ReadOnlyHashTable<Allocator, Clock>; using ReadOnlyBase = ReadOnlyHashTable<Allocator, Clock>;
using WritableBase = typename ReadWrite::WritableHashTable<Allocator>; using WritableBase = typename ReadWrite::WritableHashTable<Allocator>;
using HashTable = typename ReadOnlyBase::HashTable; using HashTable = typename ReadOnlyBase::HashTable;
@ -183,32 +155,27 @@ public:
using Value = typename ReadOnlyBase::Value; using Value = typename ReadOnlyBase::Value;
using ISerializerPtr = typename WritableBase::ISerializerPtr; using ISerializerPtr = typename WritableBase::ISerializerPtr;
WritableHashTable( WritableHashTable(HashTable& hashTable,
HashTable& hashTable,
IEpochActionManager& epochManager, IEpochActionManager& epochManager,
std::uint64_t maxCacheSizeInBytes, std::uint64_t maxCacheSizeInBytes,
std::chrono::seconds recordTimeToLive, std::chrono::seconds recordTimeToLive,
bool forceTimeBasedEviction) bool forceTimeBasedEviction)
: ReadOnlyBase::Base( : ReadOnlyBase::Base(
hashTable, hashTable,
RecordSerializer{ RecordSerializer{hashTable.m_setting.m_fixedKeySize,
hashTable.m_setting.m_fixedKeySize,
hashTable.m_setting.m_fixedValueSize, hashTable.m_setting.m_fixedValueSize,
Metadata::c_metaDataSize }) Metadata::c_metaDataSize}),
, ReadOnlyBase(hashTable, recordTimeToLive) ReadOnlyBase(hashTable, recordTimeToLive),
, WritableBase(hashTable, epochManager) WritableBase(hashTable, epochManager),
, m_maxCacheSizeInBytes{ maxCacheSizeInBytes } m_maxCacheSizeInBytes{maxCacheSizeInBytes},
, m_forceTimeBasedEviction{ forceTimeBasedEviction } m_forceTimeBasedEviction{forceTimeBasedEviction},
, m_currentEvictBucketIndex{ 0U } m_currentEvictBucketIndex{0U} {}
{}
using ReadOnlyBase::Get; using ReadOnlyBase::Get;
using ReadOnlyBase::GetPerfData; using ReadOnlyBase::GetPerfData;
virtual void Add(const Key& key, const Value& value) override virtual void Add(const Key& key, const Value& value) override {
{ if (m_forceTimeBasedEviction) {
if (m_forceTimeBasedEviction)
{
EvictBasedOnTime(key); EvictBasedOnTime(key);
} }
@ -217,42 +184,36 @@ public:
WritableBase::Add(CreateRecordBuffer(key, value)); WritableBase::Add(CreateRecordBuffer(key, value));
} }
virtual ISerializerPtr GetSerializer() const override virtual ISerializerPtr GetSerializer() const override {
{
throw std::runtime_error("Not implemented yet."); throw std::runtime_error("Not implemented yet.");
} }
private: private:
using Mutex = std::mutex; using Mutex = std::mutex;
using Lock = std::lock_guard<Mutex>; using Lock = std::lock_guard<Mutex>;
void EvictBasedOnTime(const Key& key) void EvictBasedOnTime(const Key& key) {
{
const auto bucketIndex = this->GetBucketInfo(key).first; const auto bucketIndex = this->GetBucketInfo(key).first;
auto* entry = &(this->m_hashTable.m_buckets[bucketIndex]); auto* entry = &(this->m_hashTable.m_buckets[bucketIndex]);
const auto curEpochTime = this->GetCurrentEpochTime(); const auto curEpochTime = this->GetCurrentEpochTime();
typename HashTable::Lock lock{ this->m_hashTable.GetMutex(bucketIndex) }; typename HashTable::Lock lock{this->m_hashTable.GetMutex(bucketIndex)};
while (entry != nullptr) while (entry != nullptr) {
{ for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) {
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i)
{
const auto data = entry->m_dataList[i].Load(std::memory_order_relaxed); const auto data = entry->m_dataList[i].Load(std::memory_order_relaxed);
if (data != nullptr) if (data != nullptr) {
{
const Metadata metadata{ const Metadata metadata{
const_cast<std::uint32_t*>( const_cast<std::uint32_t*>(reinterpret_cast<const std::uint32_t*>(
reinterpret_cast<const std::uint32_t*>( this->m_recordSerializer.Deserialize(*data).m_value.m_data))};
this->m_recordSerializer.Deserialize(*data).m_value.m_data)) };
if (metadata.IsExpired(curEpochTime, this->m_recordTimeToLive)) if (metadata.IsExpired(curEpochTime, this->m_recordTimeToLive)) {
{
WritableBase::Remove(*entry, i); WritableBase::Remove(*entry, i);
this->m_hashTable.m_perfData.Increment(HashTablePerfCounter::EvictedRecordsCount); this->m_hashTable.m_perfData.Increment(
HashTablePerfCounter::EvictedRecordsCount);
} }
} }
} }
@ -261,72 +222,73 @@ private:
} }
} }
// Evict uses CLOCK algorithm to evict records based on expiration and access status // Evict uses CLOCK algorithm to evict records based on expiration and access
// until the number of bytes freed match the given number of bytes needed. // status until the number of bytes freed match the given number of bytes
void Evict(std::uint64_t bytesNeeded) // needed.
{ void Evict(std::uint64_t bytesNeeded) {
std::uint64_t numBytesToFree = CalculateNumBytesToFree(bytesNeeded); std::uint64_t numBytesToFree = CalculateNumBytesToFree(bytesNeeded);
if (numBytesToFree == 0U) if (numBytesToFree == 0U) {
{
return; return;
} }
// Start evicting records with a lock. // Start evicting records with a lock.
Lock evictLock{ m_evictMutex }; Lock evictLock{m_evictMutex};
// Recalculate the number of bytes to free since other thread may have already evicted. // Recalculate the number of bytes to free since other thread may have
// already evicted.
numBytesToFree = CalculateNumBytesToFree(bytesNeeded); numBytesToFree = CalculateNumBytesToFree(bytesNeeded);
if (numBytesToFree == 0U) if (numBytesToFree == 0U) {
{
return; return;
} }
const auto curEpochTime = this->GetCurrentEpochTime(); const auto curEpochTime = this->GetCurrentEpochTime();
// The max number of iterations we are going through per eviction is twice the number // The max number of iterations we are going through per eviction is twice
// of buckets so that it can clear the access status. Note that this is the worst // the number of buckets so that it can clear the access status. Note that
// case scenario and the eviction process should exit much quicker in a normal case. // this is the worst case scenario and the eviction process should exit much
// quicker in a normal case.
auto& buckets = this->m_hashTable.m_buckets; auto& buckets = this->m_hashTable.m_buckets;
std::uint64_t numIterationsRemaining = buckets.size() * 2U; std::uint64_t numIterationsRemaining = buckets.size() * 2U;
while (numBytesToFree > 0U && numIterationsRemaining-- > 0U) while (numBytesToFree > 0U && numIterationsRemaining-- > 0U) {
{ const auto currentBucketIndex =
const auto currentBucketIndex = m_currentEvictBucketIndex++ % buckets.size(); m_currentEvictBucketIndex++ % buckets.size();
auto& bucket = buckets[currentBucketIndex]; auto& bucket = buckets[currentBucketIndex];
// Lock the bucket since another thread can bypass Evict() since TotalDataSize can // Lock the bucket since another thread can bypass Evict() since
// be updated before the lock on m_evictMutex is released. // TotalDataSize can be updated before the lock on m_evictMutex is
typename HashTable::UniqueLock lock{ this->m_hashTable.GetMutex(currentBucketIndex) }; // released.
typename HashTable::UniqueLock lock{
this->m_hashTable.GetMutex(currentBucketIndex)};
typename HashTable::Entry* entry = &bucket; typename HashTable::Entry* entry = &bucket;
while (entry != nullptr) while (entry != nullptr) {
{ for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) {
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) const auto data =
{ entry->m_dataList[i].Load(std::memory_order_relaxed);
const auto data = entry->m_dataList[i].Load(std::memory_order_relaxed);
if (data != nullptr) if (data != nullptr) {
{
const auto record = this->m_recordSerializer.Deserialize(*data); const auto record = this->m_recordSerializer.Deserialize(*data);
const auto& value = record.m_value; const auto& value = record.m_value;
Metadata metadata{ Metadata metadata{const_cast<std::uint32_t*>(
const_cast<std::uint32_t*>( reinterpret_cast<const std::uint32_t*>(value.m_data))};
reinterpret_cast<const std::uint32_t*>(
value.m_data)) };
// Evict this record if // Evict this record if
// 1: the record is expired, or // 1: the record is expired, or
// 2: the entry is not recently accessed (and unset the access bit if set). // 2: the entry is not recently accessed (and unset the access bit
if (metadata.IsExpired(curEpochTime, this->m_recordTimeToLive) // if set).
|| !metadata.UpdateAccessStatus(false)) if (metadata.IsExpired(curEpochTime, this->m_recordTimeToLive) ||
{ !metadata.UpdateAccessStatus(false)) {
const auto numBytesFreed = record.m_key.m_size + value.m_size; const auto numBytesFreed = record.m_key.m_size + value.m_size;
numBytesToFree = (numBytesFreed >= numBytesToFree) ? 0U : numBytesToFree - numBytesFreed; numBytesToFree = (numBytesFreed >= numBytesToFree)
? 0U
: numBytesToFree - numBytesFreed;
WritableBase::Remove(*entry, i); WritableBase::Remove(*entry, i);
this->m_hashTable.m_perfData.Increment(HashTablePerfCounter::EvictedRecordsCount); this->m_hashTable.m_perfData.Increment(
HashTablePerfCounter::EvictedRecordsCount);
} }
} }
} }
@ -338,18 +300,16 @@ private:
// Given the number of bytes needed, it calculates the number of bytes // Given the number of bytes needed, it calculates the number of bytes
// to free based on the max cache size. // to free based on the max cache size.
std::uint64_t CalculateNumBytesToFree(std::uint64_t bytesNeeded) const std::uint64_t CalculateNumBytesToFree(std::uint64_t bytesNeeded) const {
{
const auto& perfData = GetPerfData(); const auto& perfData = GetPerfData();
const std::uint64_t totalDataSize = const std::uint64_t totalDataSize =
perfData.Get(HashTablePerfCounter::TotalKeySize) perfData.Get(HashTablePerfCounter::TotalKeySize) +
+ perfData.Get(HashTablePerfCounter::TotalValueSize) perfData.Get(HashTablePerfCounter::TotalValueSize) +
+ perfData.Get(HashTablePerfCounter::TotalIndexSize); perfData.Get(HashTablePerfCounter::TotalIndexSize);
if ((bytesNeeded < m_maxCacheSizeInBytes) if ((bytesNeeded < m_maxCacheSizeInBytes) &&
&& (totalDataSize + bytesNeeded <= m_maxCacheSizeInBytes)) (totalDataSize + bytesNeeded <= m_maxCacheSizeInBytes)) {
{
// There are enough free bytes. // There are enough free bytes.
return 0U; return 0U;
} }
@ -364,22 +324,22 @@ private:
: bytesNeeded; : bytesNeeded;
} }
RecordBuffer* CreateRecordBuffer(const Key& key, const Value& value) RecordBuffer* CreateRecordBuffer(const Key& key, const Value& value) {
{ const auto bufferSize =
const auto bufferSize = this->m_recordSerializer.CalculateBufferSize(key, value); this->m_recordSerializer.CalculateBufferSize(key, value);
auto buffer = Detail::to_raw_pointer( auto buffer = Detail::to_raw_pointer(
this->m_hashTable.template GetAllocator<std::uint8_t>().allocate(bufferSize)); this->m_hashTable.template GetAllocator<std::uint8_t>().allocate(
bufferSize));
std::uint32_t metaDataBuffer; std::uint32_t metaDataBuffer;
Metadata{ &metaDataBuffer, this->GetCurrentEpochTime() }; Metadata{&metaDataBuffer, this->GetCurrentEpochTime()};
// 4-byte Metadata is inserted between key and value buffer. // 4-byte Metadata is inserted between key and value buffer.
return this->m_recordSerializer.Serialize( return this->m_recordSerializer.Serialize(
key, key, value,
value, Value{reinterpret_cast<std::uint8_t*>(&metaDataBuffer),
Value{ reinterpret_cast<std::uint8_t*>(&metaDataBuffer), sizeof(metaDataBuffer) }, sizeof(metaDataBuffer)},
buffer, buffer, bufferSize);
bufferSize);
} }
Mutex m_evictMutex; Mutex m_evictMutex;

Просмотреть файл

@ -4,75 +4,56 @@
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
namespace L4 namespace L4 {
{ namespace HashTable {
namespace HashTable namespace Cache {
{
namespace Cache
{
// Metadata class that stores caching related data. // Metadata class that stores caching related data.
// It stores access bit to indicate whether a record is recently accessed // It stores access bit to indicate whether a record is recently accessed
// as well as the epoch time when a record is created. // as well as the epoch time when a record is created.
// Note that this works regardless of the alignment of the metadata passed in. // Note that this works regardless of the alignment of the metadata passed in.
class Metadata class Metadata {
{ public:
public:
// Constructs Metadata with the current epoch time. // Constructs Metadata with the current epoch time.
Metadata(std::uint32_t* metadata, std::chrono::seconds curEpochTime) Metadata(std::uint32_t* metadata, std::chrono::seconds curEpochTime)
: Metadata{ metadata } : Metadata{metadata} {
{
*m_metadata = curEpochTime.count() & s_epochTimeMask; *m_metadata = curEpochTime.count() & s_epochTimeMask;
} }
explicit Metadata(std::uint32_t* metadata) explicit Metadata(std::uint32_t* metadata) : m_metadata{metadata} {
: m_metadata{ metadata }
{
assert(m_metadata != nullptr); assert(m_metadata != nullptr);
} }
// Returns the stored epoch time. // Returns the stored epoch time.
std::chrono::seconds GetEpochTime() const std::chrono::seconds GetEpochTime() const {
{
// *m_metadata even on the not-aligned memory should be fine since // *m_metadata even on the not-aligned memory should be fine since
// only the byte that contains the access bit is modified, and // only the byte that contains the access bit is modified, and
// byte read is atomic. // byte read is atomic.
return std::chrono::seconds{ *m_metadata & s_epochTimeMask }; return std::chrono::seconds{*m_metadata & s_epochTimeMask};
} }
// Returns true if the stored epoch time is expired based // Returns true if the stored epoch time is expired based
// on the given current epoch time and time-to-live value. // on the given current epoch time and time-to-live value.
bool IsExpired( bool IsExpired(std::chrono::seconds curEpochTime,
std::chrono::seconds curEpochTime, std::chrono::seconds timeToLive) const {
std::chrono::seconds timeToLive) const
{
assert(curEpochTime >= GetEpochTime()); assert(curEpochTime >= GetEpochTime());
return (curEpochTime - GetEpochTime()) > timeToLive; return (curEpochTime - GetEpochTime()) > timeToLive;
} }
// Returns true if the access status is on. // Returns true if the access status is on.
bool IsAccessed() const bool IsAccessed() const { return !!(GetAccessByte() & s_accessSetMask); }
{
return !!(GetAccessByte() & s_accessSetMask);
}
// If "set" is true, turn on the access bit in the given metadata and store it. // If "set" is true, turn on the access bit in the given metadata and store
// If "set" is false, turn off the access bit. // it. If "set" is false, turn off the access bit. Returns true if the given
// Returns true if the given metadata's access bit was originally on. // metadata's access bit was originally on.
bool UpdateAccessStatus(bool set) bool UpdateAccessStatus(bool set) {
{
const auto isAccessBitOn = IsAccessed(); const auto isAccessBitOn = IsAccessed();
// Set the bit only if the bit is not set, and vice versa. // Set the bit only if the bit is not set, and vice versa.
if (set != isAccessBitOn) if (set != isAccessBitOn) {
{ if (set) {
if (set)
{
GetAccessByte() |= s_accessSetMask; GetAccessByte() |= s_accessSetMask;
} } else {
else
{
GetAccessByte() &= s_accessUnsetMask; GetAccessByte() &= s_accessUnsetMask;
} }
} }
@ -82,14 +63,12 @@ public:
static constexpr std::uint16_t c_metaDataSize = sizeof(std::uint32_t); static constexpr std::uint16_t c_metaDataSize = sizeof(std::uint32_t);
private: private:
std::uint8_t GetAccessByte() const std::uint8_t GetAccessByte() const {
{
return reinterpret_cast<std::uint8_t*>(m_metadata)[s_accessBitByte]; return reinterpret_cast<std::uint8_t*>(m_metadata)[s_accessBitByte];
} }
std::uint8_t& GetAccessByte() std::uint8_t& GetAccessByte() {
{
return reinterpret_cast<std::uint8_t*>(m_metadata)[s_accessBitByte]; return reinterpret_cast<std::uint8_t*>(m_metadata)[s_accessBitByte];
} }
@ -110,7 +89,6 @@ private:
std::uint32_t* m_metadata = nullptr; std::uint32_t* m_metadata = nullptr;
}; };
} // namespace Cache } // namespace Cache
} // namespace HashTable } // namespace HashTable
} // namespace L4 } // namespace L4

Просмотреть файл

@ -4,88 +4,68 @@
#include "HashTable/IHashTable.h" #include "HashTable/IHashTable.h"
#include "Utils/Exception.h" #include "Utils/Exception.h"
namespace L4 namespace L4 {
{ namespace HashTable {
namespace HashTable
{
// Record struct consists of key and value pair. // Record struct consists of key and value pair.
struct Record struct Record {
{
using Key = IReadOnlyHashTable::Key; using Key = IReadOnlyHashTable::Key;
using Value = IReadOnlyHashTable::Value; using Value = IReadOnlyHashTable::Value;
Record() = default; Record() = default;
Record( Record(const Key& key, const Value& value) : m_key{key}, m_value{value} {}
const Key& key,
const Value& value)
: m_key{ key }
, m_value{ value }
{}
Key m_key; Key m_key;
Value m_value; Value m_value;
}; };
// RecordBuffer is a thin wrapper struct around a raw buffer array (pointer). // RecordBuffer is a thin wrapper struct around a raw buffer array (pointer).
struct RecordBuffer struct RecordBuffer {
{
std::uint8_t m_buffer[1]; std::uint8_t m_buffer[1];
}; };
static_assert( static_assert(sizeof(RecordBuffer) == 1,
sizeof(RecordBuffer) == 1,
"RecordBuffer size should be 1 to be a thin wrapper."); "RecordBuffer size should be 1 to be a thin wrapper.");
// RecordSerializer provides a functionality to serialize/deserialize a record information. // RecordSerializer provides a functionality to serialize/deserialize a record
class RecordSerializer // information.
{ class RecordSerializer {
public: public:
using Key = Record::Key; using Key = Record::Key;
using Value = Record::Value; using Value = Record::Value;
using KeySize = Key::size_type; using KeySize = Key::size_type;
using ValueSize = Value::size_type; using ValueSize = Value::size_type;
RecordSerializer( RecordSerializer(KeySize fixedKeySize,
KeySize fixedKeySize,
ValueSize fixedValueSize, ValueSize fixedValueSize,
ValueSize metadataSize = 0U) ValueSize metadataSize = 0U)
: m_fixedKeySize{ fixedKeySize } : m_fixedKeySize{fixedKeySize},
, m_fixedValueSize{ fixedValueSize } m_fixedValueSize{fixedValueSize},
, m_metadataSize{ metadataSize } m_metadataSize{metadataSize} {}
{}
// Returns the number of bytes needed for serializing the given key and value. // Returns the number of bytes needed for serializing the given key and value.
std::size_t CalculateBufferSize(const Key& key, const Value& value) const std::size_t CalculateBufferSize(const Key& key, const Value& value) const {
{ return ((m_fixedKeySize != 0) ? m_fixedKeySize
return : (key.m_size + sizeof(KeySize))) +
((m_fixedKeySize != 0) ((m_fixedValueSize != 0)
? m_fixedKeySize
: (key.m_size + sizeof(KeySize)))
+ ((m_fixedValueSize != 0)
? m_fixedValueSize + m_metadataSize ? m_fixedValueSize + m_metadataSize
: (value.m_size + sizeof(ValueSize) + m_metadataSize)); : (value.m_size + sizeof(ValueSize) + m_metadataSize));
} }
// Returns the number bytes used for key and value sizes. // Returns the number bytes used for key and value sizes.
std::size_t CalculateRecordOverhead() const std::size_t CalculateRecordOverhead() const {
{ return (m_fixedKeySize != 0 ? 0U : sizeof(KeySize)) +
return (m_fixedValueSize != 0 ? 0U : sizeof(ValueSize));
(m_fixedKeySize != 0 ? 0U : sizeof(KeySize))
+ (m_fixedValueSize != 0 ? 0U : sizeof(ValueSize));
} }
// Serializes the given key and value to the given buffer. // Serializes the given key and value to the given buffer.
// Note that the buffer size is at least as big as the number of bytes // Note that the buffer size is at least as big as the number of bytes
// returned by CalculateBufferSize(). // returned by CalculateBufferSize().
RecordBuffer* Serialize( RecordBuffer* Serialize(const Key& key,
const Key& key,
const Value& value, const Value& value,
std::uint8_t* const buffer, std::uint8_t* const buffer,
std::size_t bufferSize) const std::size_t bufferSize) const {
{
Validate(key, value); Validate(key, value);
assert(CalculateBufferSize(key, value) <= bufferSize); assert(CalculateBufferSize(key, value) <= bufferSize);
@ -95,7 +75,8 @@ public:
#if defined(_MSC_VER) #if defined(_MSC_VER)
memcpy_s(buffer + start, key.m_size, key.m_data, key.m_size); memcpy_s(buffer + start, key.m_size, key.m_data, key.m_size);
memcpy_s(buffer + start + key.m_size, value.m_size, value.m_data, value.m_size); memcpy_s(buffer + start + key.m_size, value.m_size, value.m_data,
value.m_size);
#else #else
memcpy(buffer + start, key.m_data, key.m_size); memcpy(buffer + start, key.m_data, key.m_size);
memcpy(buffer + start + key.m_size, value.m_data, value.m_size); memcpy(buffer + start + key.m_size, value.m_data, value.m_size);
@ -107,57 +88,52 @@ public:
// The meta value is serialized between key and value. // The meta value is serialized between key and value.
// Note that the buffer size is at least as big as the number of bytes // Note that the buffer size is at least as big as the number of bytes
// returned by CalculateBufferSize(). // returned by CalculateBufferSize().
RecordBuffer* Serialize( RecordBuffer* Serialize(const Key& key,
const Key& key,
const Value& value, const Value& value,
const Value& metaValue, const Value& metaValue,
std::uint8_t* const buffer, std::uint8_t* const buffer,
std::size_t bufferSize) const std::size_t bufferSize) const {
{
Validate(key, value, metaValue); Validate(key, value, metaValue);
assert(CalculateBufferSize(key, value) <= bufferSize); assert(CalculateBufferSize(key, value) <= bufferSize);
(void)bufferSize; (void)bufferSize;
const auto start = SerializeSizes(buffer, key.m_size, value.m_size + metaValue.m_size); const auto start =
SerializeSizes(buffer, key.m_size, value.m_size + metaValue.m_size);
#if defined(_MSC_VER) #if defined(_MSC_VER)
memcpy_s(buffer + start, key.m_size, key.m_data, key.m_size); memcpy_s(buffer + start, key.m_size, key.m_data, key.m_size);
memcpy_s(buffer + start + key.m_size, metaValue.m_size, metaValue.m_data, metaValue.m_size); memcpy_s(buffer + start + key.m_size, metaValue.m_size, metaValue.m_data,
memcpy_s(buffer + start + key.m_size + metaValue.m_size, value.m_size, value.m_data, value.m_size); metaValue.m_size);
memcpy_s(buffer + start + key.m_size + metaValue.m_size, value.m_size,
value.m_data, value.m_size);
#else #else
memcpy(buffer + start, key.m_data, key.m_size); memcpy(buffer + start, key.m_data, key.m_size);
memcpy(buffer + start + key.m_size, metaValue.m_data, metaValue.m_size); memcpy(buffer + start + key.m_size, metaValue.m_data, metaValue.m_size);
memcpy(buffer + start + key.m_size + metaValue.m_size, value.m_data, value.m_size); memcpy(buffer + start + key.m_size + metaValue.m_size, value.m_data,
value.m_size);
#endif #endif
return reinterpret_cast<RecordBuffer*>(buffer); return reinterpret_cast<RecordBuffer*>(buffer);
} }
// Deserializes the given buffer and returns a Record object. // Deserializes the given buffer and returns a Record object.
Record Deserialize(const RecordBuffer& buffer) const Record Deserialize(const RecordBuffer& buffer) const {
{
Record record; Record record;
const auto* dataBuffer = buffer.m_buffer; const auto* dataBuffer = buffer.m_buffer;
auto& key = record.m_key; auto& key = record.m_key;
if (m_fixedKeySize != 0) if (m_fixedKeySize != 0) {
{
key.m_size = m_fixedKeySize; key.m_size = m_fixedKeySize;
} } else {
else
{
key.m_size = *reinterpret_cast<const KeySize*>(dataBuffer); key.m_size = *reinterpret_cast<const KeySize*>(dataBuffer);
dataBuffer += sizeof(KeySize); dataBuffer += sizeof(KeySize);
} }
auto& value = record.m_value; auto& value = record.m_value;
if (m_fixedValueSize != 0) if (m_fixedValueSize != 0) {
{
value.m_size = m_fixedValueSize + m_metadataSize; value.m_size = m_fixedValueSize + m_metadataSize;
} } else {
else
{
value.m_size = *reinterpret_cast<const ValueSize*>(dataBuffer); value.m_size = *reinterpret_cast<const ValueSize*>(dataBuffer);
dataBuffer += sizeof(ValueSize); dataBuffer += sizeof(ValueSize);
} }
@ -168,45 +144,39 @@ public:
return record; return record;
} }
private: private:
// Validates key and value sizes when fixed sizes are set. // Validates key and value sizes when fixed sizes are set.
// Throws an exception if invalid sizes are used. // Throws an exception if invalid sizes are used.
void Validate(const Key& key, const Value& value) const void Validate(const Key& key, const Value& value) const {
{ if ((m_fixedKeySize != 0 && key.m_size != m_fixedKeySize) ||
if ((m_fixedKeySize != 0 && key.m_size != m_fixedKeySize) (m_fixedValueSize != 0 && value.m_size != m_fixedValueSize)) {
|| (m_fixedValueSize != 0 && value.m_size != m_fixedValueSize))
{
throw RuntimeException("Invalid key or value sizes are given."); throw RuntimeException("Invalid key or value sizes are given.");
} }
} }
// Validates against the given meta value. // Validates against the given meta value.
void Validate(const Key& key, const Value& value, const Value& metaValue) const void Validate(const Key& key,
{ const Value& value,
const Value& metaValue) const {
Validate(key, value); Validate(key, value);
if (m_metadataSize != metaValue.m_size) if (m_metadataSize != metaValue.m_size) {
{
throw RuntimeException("Invalid meta value size is given."); throw RuntimeException("Invalid meta value size is given.");
} }
} }
// Serializes size information to the given buffer. // Serializes size information to the given buffer.
// It assumes that buffer has enough size for serialization. // It assumes that buffer has enough size for serialization.
std::size_t SerializeSizes( std::size_t SerializeSizes(std::uint8_t* const buffer,
std::uint8_t* const buffer,
KeySize keySize, KeySize keySize,
ValueSize valueSize) const ValueSize valueSize) const {
{
auto curBuffer = buffer; auto curBuffer = buffer;
if (m_fixedKeySize == 0) if (m_fixedKeySize == 0) {
{
*reinterpret_cast<KeySize*>(curBuffer) = keySize; *reinterpret_cast<KeySize*>(curBuffer) = keySize;
curBuffer += sizeof(keySize); curBuffer += sizeof(keySize);
} }
if (m_fixedValueSize == 0) if (m_fixedValueSize == 0) {
{
*reinterpret_cast<ValueSize*>(curBuffer) = valueSize; *reinterpret_cast<ValueSize*>(curBuffer) = valueSize;
curBuffer += sizeof(valueSize); curBuffer += sizeof(valueSize);
} }
@ -219,6 +189,5 @@ private:
const ValueSize m_metadataSize; const ValueSize m_metadataSize;
}; };
} // namespace HashTable } // namespace HashTable
} // namespace L4 } // namespace L4

Просмотреть файл

@ -4,23 +4,21 @@
#include "HashTable/Common/SharedHashTable.h" #include "HashTable/Common/SharedHashTable.h"
#include "HashTable/Config.h" #include "HashTable/Config.h"
namespace L4 namespace L4 {
{ namespace HashTable {
namespace HashTable
{
// SettingAdapter class provides a functionality to convert a HashTableConfig::Setting object // SettingAdapter class provides a functionality to convert a
// to a SharedHashTable::Setting object. // HashTableConfig::Setting object to a SharedHashTable::Setting object.
class SettingAdapter class SettingAdapter {
{ public:
public:
template <typename SharedHashTable> template <typename SharedHashTable>
typename SharedHashTable::Setting Convert(const HashTableConfig::Setting& from) const typename SharedHashTable::Setting Convert(
{ const HashTableConfig::Setting& from) const {
typename SharedHashTable::Setting to; typename SharedHashTable::Setting to;
to.m_numBuckets = from.m_numBuckets; to.m_numBuckets = from.m_numBuckets;
to.m_numBucketsPerMutex = (std::max)(from.m_numBucketsPerMutex.get_value_or(1U), 1U); to.m_numBucketsPerMutex =
(std::max)(from.m_numBucketsPerMutex.get_value_or(1U), 1U);
to.m_fixedKeySize = from.m_fixedKeySize.get_value_or(0U); to.m_fixedKeySize = from.m_fixedKeySize.get_value_or(0U);
to.m_fixedValueSize = from.m_fixedValueSize.get_value_or(0U); to.m_fixedValueSize = from.m_fixedValueSize.get_value_or(0U);

Просмотреть файл

@ -10,15 +10,12 @@
#include "Utils/Exception.h" #include "Utils/Exception.h"
#include "Utils/Lock.h" #include "Utils/Lock.h"
namespace L4 namespace L4 {
{ namespace HashTable {
namespace HashTable
{
// SharedHashTable struct represents the hash table structure. // SharedHashTable struct represents the hash table structure.
template <typename TData, typename TAllocator> template <typename TData, typename TAllocator>
struct SharedHashTable struct SharedHashTable {
{
using Data = TData; using Data = TData;
using Allocator = TAllocator; using Allocator = TAllocator;
@ -45,72 +42,73 @@ struct SharedHashTable
// | Data16 pointer | 18 // | Data16 pointer | 18
// | Entry pointer to the next Entry | 19 // | Entry pointer to the next Entry | 19
// <----------------------8 bytes ----------------------------------> // <----------------------8 bytes ---------------------------------->
// , where tag1 is a tag for Data1, tag2 for Data2, and so on. A tag value can be looked up // , where tag1 is a tag for Data1, tag2 for Data2, and so on. A tag value can
// first before going to the corresponding Data for a quick check. // be looked up first before going to the corresponding Data for a quick
// Also note that a byte read is atomic in modern processors so that tag is just // check. Also note that a byte read is atomic in modern processors so that
// std::uint8_t instead of being atomic. Even in the case where the tag value read is a garbage , // tag is just std::uint8_t instead of being atomic. Even in the case where
// this is acceptable because of the followings: // the tag value read is a garbage , this is acceptable because of the
// 1) if the garbage value was a hit where it should have been a miss: the actual key comparison will fail, // followings:
// 2) if the garbage value was a miss where it should have been a hit: the key value must // 1) if the garbage value was a hit where it should have been a miss: the
// have been changed since the tag was changed, so it will be looked up correctly // actual key comparison will fail, 2) if the garbage value was a miss
// after the tag value written is visible correctly. Note that we don't need to guarantee the timing of // where it should have been a hit: the key value must
// writing and reading (meaning the value written should be visible to the reader right away). // have been changed since the tag was changed, so it will be looked up
// correctly after the tag value written is visible correctly. Note that
// we don't need to guarantee the timing of writing and reading (meaning
// the value written should be visible to the reader right away).
// //
// Note about the CPU cache. In previous implementation, the Entry was 64 bytes to fit in the CPU cache. // Note about the CPU cache. In previous implementation, the Entry was 64
// However, this resulted in lots of wasted space. For example, when the ratio of the number of expected records // bytes to fit in the CPU cache. However, this resulted in lots of wasted
// to the number of buckets was 2:1, only 85% buckets were occupied. After experiments, if you have 10:1 ratio, // space. For example, when the ratio of the number of expected records to the
// you will have 99.98% utilization of buckets. This required having more data per Entry, and the ideal number // number of buckets was 2:1, only 85% buckets were occupied. After
// (after experiments) turned out to be 16 records per Entry. Also, because of how CPU fetches contiguous memory, // experiments, if you have 10:1 ratio, you will have 99.98% utilization of
// this didn't have any impact on micro-benchmarking. // buckets. This required having more data per Entry, and the ideal number
struct Entry // (after experiments) turned out to be 16 records per Entry. Also, because of
{ // how CPU fetches contiguous memory, this didn't have any impact on
// micro-benchmarking.
struct Entry {
Entry() = default; Entry() = default;
// Releases deallocates all the memories of the chained entries including // Releases deallocates all the memories of the chained entries including
// the data list in the current Entry. // the data list in the current Entry.
void Release(Allocator allocator) void Release(Allocator allocator) {
{ auto dataDeleter = [allocator](auto& data) {
auto dataDeleter = [allocator](auto& data)
{
auto dataToDelete = data.Load(); auto dataToDelete = data.Load();
if (dataToDelete != nullptr) if (dataToDelete != nullptr) {
{
dataToDelete->~Data(); dataToDelete->~Data();
typename Allocator::template rebind<Data>::other(allocator).deallocate(dataToDelete, 1U); typename Allocator::template rebind<Data>::other(allocator)
.deallocate(dataToDelete, 1U);
} }
}; };
// Delete all the chained entries, not including itself. // Delete all the chained entries, not including itself.
auto curEntry = m_next.Load(); auto curEntry = m_next.Load();
while (curEntry != nullptr) while (curEntry != nullptr) {
{
auto entryToDelete = curEntry; auto entryToDelete = curEntry;
// Copy m_next for the next iteration. // Copy m_next for the next iteration.
curEntry = entryToDelete->m_next.Load(); curEntry = entryToDelete->m_next.Load();
// Delete all the data within this entry. // Delete all the data within this entry.
for (auto& data : entryToDelete->m_dataList) for (auto& data : entryToDelete->m_dataList) {
{
dataDeleter(data); dataDeleter(data);
} }
// Clean the current entry itself. // Clean the current entry itself.
entryToDelete->~Entry(); entryToDelete->~Entry();
typename Allocator::template rebind<Entry>::other(allocator).deallocate(entryToDelete, 1U); typename Allocator::template rebind<Entry>::other(allocator).deallocate(
entryToDelete, 1U);
} }
// Delete all the data from the head of chained entries. // Delete all the data from the head of chained entries.
for (auto& data : m_dataList) for (auto& data : m_dataList) {
{
dataDeleter(data); dataDeleter(data);
} }
} }
static constexpr std::uint8_t c_numDataPerEntry = 16U; static constexpr std::uint8_t c_numDataPerEntry = 16U;
std::array<std::uint8_t, c_numDataPerEntry> m_tags{ 0U }; std::array<std::uint8_t, c_numDataPerEntry> m_tags{0U};
std::array<Utils::AtomicOffsetPtr<Data>, c_numDataPerEntry> m_dataList{}; std::array<Utils::AtomicOffsetPtr<Data>, c_numDataPerEntry> m_dataList{};
@ -119,23 +117,20 @@ struct SharedHashTable
static_assert(sizeof(Entry) == 152, "Entry should be 152 bytes."); static_assert(sizeof(Entry) == 152, "Entry should be 152 bytes.");
struct Setting struct Setting {
{
using KeySize = IReadOnlyHashTable::Key::size_type; using KeySize = IReadOnlyHashTable::Key::size_type;
using ValueSize = IReadOnlyHashTable::Value::size_type; using ValueSize = IReadOnlyHashTable::Value::size_type;
Setting() = default; Setting() = default;
explicit Setting( explicit Setting(std::uint32_t numBuckets,
std::uint32_t numBuckets,
std::uint32_t numBucketsPerMutex = 1U, std::uint32_t numBucketsPerMutex = 1U,
KeySize fixedKeySize = 0U, KeySize fixedKeySize = 0U,
ValueSize fixedValueSize = 0U) ValueSize fixedValueSize = 0U)
: m_numBuckets{ numBuckets } : m_numBuckets{numBuckets},
, m_numBucketsPerMutex{ numBucketsPerMutex } m_numBucketsPerMutex{numBucketsPerMutex},
, m_fixedKeySize{ fixedKeySize } m_fixedKeySize{fixedKeySize},
, m_fixedValueSize{ fixedValueSize } m_fixedValueSize{fixedValueSize} {}
{}
std::uint32_t m_numBuckets = 1U; std::uint32_t m_numBuckets = 1U;
std::uint32_t m_numBucketsPerMutex = 1U; std::uint32_t m_numBucketsPerMutex = 1U;
@ -143,29 +138,27 @@ struct SharedHashTable
ValueSize m_fixedValueSize = 0U; ValueSize m_fixedValueSize = 0U;
}; };
SharedHashTable( SharedHashTable(const Setting& setting, Allocator allocator)
const Setting& setting, : m_allocator{allocator},
Allocator allocator) m_setting{setting},
: m_allocator{ allocator } m_buckets{
, m_setting{ setting } setting.m_numBuckets,
, m_buckets{ setting.m_numBuckets, typename Allocator::template rebind<Entry>::other(m_allocator) } typename Allocator::template rebind<Entry>::other(m_allocator)},
, m_mutexes{ m_mutexes{
(std::max)(setting.m_numBuckets / (std::max)(setting.m_numBucketsPerMutex, 1U), 1U), (std::max)(setting.m_numBuckets /
typename Allocator::template rebind<Mutex>::other(m_allocator) } (std::max)(setting.m_numBucketsPerMutex, 1U),
, m_perfData{} 1U),
{ typename Allocator::template rebind<Mutex>::other(m_allocator)},
m_perfData{} {
m_perfData.Set(HashTablePerfCounter::BucketsCount, m_buckets.size()); m_perfData.Set(HashTablePerfCounter::BucketsCount, m_buckets.size());
m_perfData.Set( m_perfData.Set(HashTablePerfCounter::TotalIndexSize,
HashTablePerfCounter::TotalIndexSize, (m_buckets.size() * sizeof(Entry)) +
(m_buckets.size() * sizeof(Entry)) (m_mutexes.size() * sizeof(Mutex)) +
+ (m_mutexes.size() * sizeof(Mutex)) sizeof(SharedHashTable));
+ sizeof(SharedHashTable));
} }
~SharedHashTable() ~SharedHashTable() {
{ for (auto& bucket : m_buckets) {
for (auto& bucket : m_buckets)
{
bucket.Release(m_allocator); bucket.Release(m_allocator);
} }
} }
@ -174,17 +167,17 @@ struct SharedHashTable
using Lock = std::lock_guard<Mutex>; using Lock = std::lock_guard<Mutex>;
using UniqueLock = std::unique_lock<Mutex>; using UniqueLock = std::unique_lock<Mutex>;
using Buckets = Interprocess::Container::Vector<Entry, typename Allocator::template rebind<Entry>::other>; using Buckets = Interprocess::Container::
using Mutexes = Interprocess::Container::Vector<Mutex, typename Allocator::template rebind<Mutex>::other>; Vector<Entry, typename Allocator::template rebind<Entry>::other>;
using Mutexes = Interprocess::Container::
Vector<Mutex, typename Allocator::template rebind<Mutex>::other>;
template <typename T> template <typename T>
auto GetAllocator() const auto GetAllocator() const {
{
return typename Allocator::template rebind<T>::other(m_allocator); return typename Allocator::template rebind<T>::other(m_allocator);
} }
Mutex& GetMutex(std::size_t index) Mutex& GetMutex(std::size_t index) {
{
return m_mutexes[index % m_mutexes.size()]; return m_mutexes[index % m_mutexes.size()];
} }

Просмотреть файл

@ -2,33 +2,28 @@
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <cassert> #include <cassert>
#include <cstdint>
#include <chrono> #include <chrono>
#include <cstdint>
#include <memory> #include <memory>
#include "HashTable/IHashTable.h" #include "HashTable/IHashTable.h"
#include "Utils/Properties.h" #include "Utils/Properties.h"
namespace L4 namespace L4 {
{
// HashTableConfig struct. // HashTableConfig struct.
struct HashTableConfig struct HashTableConfig {
{ struct Setting {
struct Setting
{
using KeySize = IReadOnlyHashTable::Key::size_type; using KeySize = IReadOnlyHashTable::Key::size_type;
using ValueSize = IReadOnlyHashTable::Value::size_type; using ValueSize = IReadOnlyHashTable::Value::size_type;
explicit Setting( explicit Setting(std::uint32_t numBuckets,
std::uint32_t numBuckets,
boost::optional<std::uint32_t> numBucketsPerMutex = {}, boost::optional<std::uint32_t> numBucketsPerMutex = {},
boost::optional<KeySize> fixedKeySize = {}, boost::optional<KeySize> fixedKeySize = {},
boost::optional<ValueSize> fixedValueSize = {}) boost::optional<ValueSize> fixedValueSize = {})
: m_numBuckets{ numBuckets } : m_numBuckets{numBuckets},
, m_numBucketsPerMutex{ numBucketsPerMutex } m_numBucketsPerMutex{numBucketsPerMutex},
, m_fixedKeySize{ fixedKeySize } m_fixedKeySize{fixedKeySize},
, m_fixedValueSize{ fixedValueSize } m_fixedValueSize{fixedValueSize} {}
{}
std::uint32_t m_numBuckets; std::uint32_t m_numBuckets;
boost::optional<std::uint32_t> m_numBucketsPerMutex; boost::optional<std::uint32_t> m_numBucketsPerMutex;
@ -36,49 +31,40 @@ struct HashTableConfig
boost::optional<ValueSize> m_fixedValueSize; boost::optional<ValueSize> m_fixedValueSize;
}; };
struct Cache struct Cache {
{ Cache(std::uint64_t maxCacheSizeInBytes,
Cache(
std::uint64_t maxCacheSizeInBytes,
std::chrono::seconds recordTimeToLive, std::chrono::seconds recordTimeToLive,
bool forceTimeBasedEviction) bool forceTimeBasedEviction)
: m_maxCacheSizeInBytes{ maxCacheSizeInBytes } : m_maxCacheSizeInBytes{maxCacheSizeInBytes},
, m_recordTimeToLive{ recordTimeToLive } m_recordTimeToLive{recordTimeToLive},
, m_forceTimeBasedEviction{ forceTimeBasedEviction } m_forceTimeBasedEviction{forceTimeBasedEviction} {}
{}
std::uint64_t m_maxCacheSizeInBytes; std::uint64_t m_maxCacheSizeInBytes;
std::chrono::seconds m_recordTimeToLive; std::chrono::seconds m_recordTimeToLive;
bool m_forceTimeBasedEviction; bool m_forceTimeBasedEviction;
}; };
struct Serializer struct Serializer {
{
using Properties = Utils::Properties; using Properties = Utils::Properties;
Serializer( Serializer(std::shared_ptr<std::istream> stream = {},
std::shared_ptr<std::istream> stream = {},
boost::optional<Properties> properties = {}) boost::optional<Properties> properties = {})
: m_stream{ stream } : m_stream{stream}, m_properties{properties} {}
, m_properties{ properties }
{}
std::shared_ptr<std::istream> m_stream; std::shared_ptr<std::istream> m_stream;
boost::optional<Properties> m_properties; boost::optional<Properties> m_properties;
}; };
HashTableConfig( HashTableConfig(std::string name,
std::string name,
Setting setting, Setting setting,
boost::optional<Cache> cache = {}, boost::optional<Cache> cache = {},
boost::optional<Serializer> serializer = {}) boost::optional<Serializer> serializer = {})
: m_name{ std::move(name) } : m_name{std::move(name)},
, m_setting{ std::move(setting) } m_setting{std::move(setting)},
, m_cache{ cache } m_cache{cache},
, m_serializer{ serializer } m_serializer{serializer} {
{ assert(m_setting.m_numBuckets > 0U ||
assert(m_setting.m_numBuckets > 0U (m_serializer && (serializer->m_stream != nullptr)));
|| (m_serializer && (serializer->m_stream != nullptr)));
} }
std::string m_name; std::string m_name;

Просмотреть файл

@ -5,35 +5,26 @@
#include "Log/PerfCounter.h" #include "Log/PerfCounter.h"
#include "Utils/Properties.h" #include "Utils/Properties.h"
namespace L4 namespace L4 {
{
// IReadOnlyHashTable interface for read-only access to the hash table. // IReadOnlyHashTable interface for read-only access to the hash table.
struct IReadOnlyHashTable struct IReadOnlyHashTable {
{
// Blob struct that represents a memory blob. // Blob struct that represents a memory blob.
template <typename TSize> template <typename TSize>
struct Blob struct Blob {
{
using size_type = TSize; using size_type = TSize;
explicit Blob(const std::uint8_t* data = nullptr, size_type size = 0U) explicit Blob(const std::uint8_t* data = nullptr, size_type size = 0U)
: m_data{ data } : m_data{data}, m_size{size} {
, m_size{ size } static_assert(std::numeric_limits<size_type>::is_integer,
{ "size_type is not an integer.");
static_assert(std::numeric_limits<size_type>::is_integer, "size_type is not an integer.");
} }
bool operator==(const Blob& other) const bool operator==(const Blob& other) const {
{ return (m_size == other.m_size) && !memcmp(m_data, other.m_data, m_size);
return (m_size == other.m_size)
&& !memcmp(m_data, other.m_data, m_size);
} }
bool operator!=(const Blob& other) const bool operator!=(const Blob& other) const { return !(*this == other); }
{
return !(*this == other);
}
const std::uint8_t* m_data; const std::uint8_t* m_data;
size_type m_size; size_type m_size;
@ -56,8 +47,7 @@ struct IReadOnlyHashTable
}; };
// IReadOnlyHashTable::IIterator interface for the hash table iterator. // IReadOnlyHashTable::IIterator interface for the hash table iterator.
struct IReadOnlyHashTable::IIterator struct IReadOnlyHashTable::IIterator {
{
virtual ~IIterator() = default; virtual ~IIterator() = default;
virtual void Reset() = 0; virtual void Reset() = 0;
@ -70,8 +60,7 @@ struct IReadOnlyHashTable::IIterator
}; };
// IWritableHashTable interface for write access to the hash table. // IWritableHashTable interface for write access to the hash table.
struct IWritableHashTable : public virtual IReadOnlyHashTable struct IWritableHashTable : public virtual IReadOnlyHashTable {
{
struct ISerializer; struct ISerializer;
using ISerializerPtr = std::unique_ptr<ISerializer>; using ISerializerPtr = std::unique_ptr<ISerializer>;
@ -84,12 +73,10 @@ struct IWritableHashTable : public virtual IReadOnlyHashTable
}; };
// IWritableHashTable::ISerializer interface for serializing hash table. // IWritableHashTable::ISerializer interface for serializing hash table.
struct IWritableHashTable::ISerializer struct IWritableHashTable::ISerializer {
{
virtual ~ISerializer() = default; virtual ~ISerializer() = default;
virtual void Serialize( virtual void Serialize(std::ostream& stream,
std::ostream& stream,
const Utils::Properties& properties) = 0; const Utils::Properties& properties) = 0;
}; };

Просмотреть файл

@ -3,32 +3,29 @@
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <cstdint> #include <cstdint>
#include <mutex> #include <mutex>
#include "detail/ToRawPointer.h"
#include "Epoch/IEpochActionManager.h" #include "Epoch/IEpochActionManager.h"
#include "HashTable/Common/SharedHashTable.h"
#include "HashTable/Common/Record.h" #include "HashTable/Common/Record.h"
#include "HashTable/Common/SharedHashTable.h"
#include "HashTable/IHashTable.h" #include "HashTable/IHashTable.h"
#include "HashTable/ReadWrite/Serializer.h" #include "HashTable/ReadWrite/Serializer.h"
#include "Log/PerfCounter.h" #include "Log/PerfCounter.h"
#include "Utils/Exception.h" #include "Utils/Exception.h"
#include "Utils/MurmurHash3.h" #include "Utils/MurmurHash3.h"
#include "Utils/Properties.h" #include "Utils/Properties.h"
#include "detail/ToRawPointer.h"
namespace L4 namespace L4 {
{
// ReadWriteHashTable is a general purpose hash table where the look up is look free. // ReadWriteHashTable is a general purpose hash table where the look up is look
namespace HashTable // free.
{ namespace HashTable {
namespace ReadWrite namespace ReadWrite {
{
// ReadOnlyHashTable class implements IReadOnlyHashTable interface and provides // ReadOnlyHashTable class implements IReadOnlyHashTable interface and provides
// the functionality to read data given a key. // the functionality to read data given a key.
template <typename Allocator> template <typename Allocator>
class ReadOnlyHashTable : public virtual IReadOnlyHashTable class ReadOnlyHashTable : public virtual IReadOnlyHashTable {
{ public:
public:
using HashTable = SharedHashTable<RecordBuffer, Allocator>; using HashTable = SharedHashTable<RecordBuffer, Allocator>;
class Iterator; class Iterator;
@ -36,36 +33,30 @@ public:
explicit ReadOnlyHashTable( explicit ReadOnlyHashTable(
HashTable& hashTable, HashTable& hashTable,
boost::optional<RecordSerializer> recordSerializer = boost::none) boost::optional<RecordSerializer> recordSerializer = boost::none)
: m_hashTable{ hashTable } : m_hashTable{hashTable},
, m_recordSerializer{ m_recordSerializer{
recordSerializer recordSerializer
? *recordSerializer ? *recordSerializer
: RecordSerializer{ : RecordSerializer{m_hashTable.m_setting.m_fixedKeySize,
m_hashTable.m_setting.m_fixedKeySize, m_hashTable.m_setting.m_fixedValueSize}} {}
m_hashTable.m_setting.m_fixedValueSize } }
{}
virtual bool Get(const Key& key, Value& value) const override virtual bool Get(const Key& key, Value& value) const override {
{
const auto bucketInfo = GetBucketInfo(key); const auto bucketInfo = GetBucketInfo(key);
const auto* entry = &m_hashTable.m_buckets[bucketInfo.first]; const auto* entry = &m_hashTable.m_buckets[bucketInfo.first];
while (entry != nullptr) while (entry != nullptr) {
{ for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) {
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) if (bucketInfo.second == entry->m_tags[i]) {
{ // There could be a race condition where m_dataList[i] is updated
if (bucketInfo.second == entry->m_tags[i]) // during access. Therefore, load it once and save it (it's safe to
{ // store it b/c the memory will not be deleted until ref count becomes
// There could be a race condition where m_dataList[i] is updated during access. // 0).
// Therefore, load it once and save it (it's safe to store it b/c the memory const auto data =
// will not be deleted until ref count becomes 0). entry->m_dataList[i].Load(std::memory_order_acquire);
const auto data = entry->m_dataList[i].Load(std::memory_order_acquire);
if (data != nullptr) if (data != nullptr) {
{
const auto record = m_recordSerializer.Deserialize(*data); const auto record = m_recordSerializer.Deserialize(*data);
if (record.m_key == key) if (record.m_key == key) {
{
value = record.m_value; value = record.m_value;
return true; return true;
} }
@ -79,15 +70,14 @@ public:
return false; return false;
} }
virtual IIteratorPtr GetIterator() const override virtual IIteratorPtr GetIterator() const override {
{
return std::make_unique<Iterator>(m_hashTable, m_recordSerializer); return std::make_unique<Iterator>(m_hashTable, m_recordSerializer);
} }
virtual const HashTablePerfData& GetPerfData() const override virtual const HashTablePerfData& GetPerfData() const override {
{
// Synchronizes with any std::memory_order_release if there exists, so that // Synchronizes with any std::memory_order_release if there exists, so that
// HashTablePerfData has the latest values at the moment when GetPerfData() is called. // HashTablePerfData has the latest values at the moment when GetPerfData()
// is called.
std::atomic_thread_fence(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_acquire);
return m_hashTable.m_perfData; return m_hashTable.m_perfData;
} }
@ -95,22 +85,21 @@ public:
ReadOnlyHashTable(const ReadOnlyHashTable&) = delete; ReadOnlyHashTable(const ReadOnlyHashTable&) = delete;
ReadOnlyHashTable& operator=(const ReadOnlyHashTable&) = delete; ReadOnlyHashTable& operator=(const ReadOnlyHashTable&) = delete;
protected: protected:
// GetBucketInfo returns a pair, where the first is the index to the bucket // GetBucketInfo returns a pair, where the first is the index to the bucket
// and the second is the tag value for the given key. // and the second is the tag value for the given key.
// In this hash table, we treat tag value of 0 as empty (see WritableHashTable::Remove()), // In this hash table, we treat tag value of 0 as empty (see
// so in the worst case scenario, where an entry has an empty data list and the tag // WritableHashTable::Remove()), so in the worst case scenario, where an entry
// value returned for the key is 0, the look up cost is up to 6 checks. We can do something // has an empty data list and the tag value returned for the key is 0, the
// smarter by using the unused two bytes per Entry, but since an Entry object fits into // look up cost is up to 6 checks. We can do something smarter by using the
// CPU cache, the extra overhead should be minimal. // unused two bytes per Entry, but since an Entry object fits into CPU cache,
std::pair<std::uint32_t, std::uint8_t> GetBucketInfo(const Key& key) const // the extra overhead should be minimal.
{ std::pair<std::uint32_t, std::uint8_t> GetBucketInfo(const Key& key) const {
std::array<std::uint64_t, 2> hash; std::array<std::uint64_t, 2> hash;
MurmurHash3_x64_128(key.m_data, key.m_size, 0U, hash.data()); MurmurHash3_x64_128(key.m_data, key.m_size, 0U, hash.data());
return { return {static_cast<std::uint32_t>(hash[0] % m_hashTable.m_buckets.size()),
static_cast<std::uint32_t>(hash[0] % m_hashTable.m_buckets.size()), static_cast<std::uint8_t>(hash[1])};
static_cast<std::uint8_t>(hash[1]) };
} }
HashTable& m_hashTable; HashTable& m_hashTable;
@ -118,69 +107,57 @@ protected:
RecordSerializer m_recordSerializer; RecordSerializer m_recordSerializer;
}; };
// ReadOnlyHashTable::Iterator class implements IIterator interface and provides // ReadOnlyHashTable::Iterator class implements IIterator interface and provides
// read-only iterator for the ReadOnlyHashTable. // read-only iterator for the ReadOnlyHashTable.
template <typename Allocator> template <typename Allocator>
class ReadOnlyHashTable<Allocator>::Iterator : public IIterator class ReadOnlyHashTable<Allocator>::Iterator : public IIterator {
{ public:
public: Iterator(const HashTable& hashTable,
Iterator(
const HashTable& hashTable,
const RecordSerializer& recordDeserializer) const RecordSerializer& recordDeserializer)
: m_hashTable{ hashTable } : m_hashTable{hashTable},
, m_recordSerializer{ recordDeserializer } m_recordSerializer{recordDeserializer},
, m_currentBucketIndex{ -1 } m_currentBucketIndex{-1},
, m_currentRecordIndex{ 0U } m_currentRecordIndex{0U},
, m_currentEntry{ nullptr } m_currentEntry{nullptr} {}
{}
Iterator(Iterator&& iterator) Iterator(Iterator&& iterator)
: m_hashTable{ std::move(iterator.m_hashTable) } : m_hashTable{std::move(iterator.m_hashTable)},
, m_recordSerializer{ std::move(iterator.recordDeserializer) } m_recordSerializer{std::move(iterator.recordDeserializer)},
, m_currentBucketIndex{ std::move(iterator.m_currentBucketIndex) } m_currentBucketIndex{std::move(iterator.m_currentBucketIndex)},
, m_currentRecordIndex{ std::move(iterator.m_currentRecordIndex) } m_currentRecordIndex{std::move(iterator.m_currentRecordIndex)},
, m_currentEntry{ std::move(iterator.m_currentEntry) } m_currentEntry{std::move(iterator.m_currentEntry)} {}
{}
void Reset() override void Reset() override {
{
m_currentBucketIndex = -1; m_currentBucketIndex = -1;
m_currentRecordIndex = 0U; m_currentRecordIndex = 0U;
m_currentEntry = nullptr; m_currentEntry = nullptr;
} }
bool MoveNext() override bool MoveNext() override {
{ if (IsEnd()) {
if (IsEnd())
{
return false; return false;
} }
if (m_currentEntry != nullptr) if (m_currentEntry != nullptr) {
{
MoveToNextData(); MoveToNextData();
} }
assert(m_currentRecordIndex < HashTable::Entry::c_numDataPerEntry); assert(m_currentRecordIndex < HashTable::Entry::c_numDataPerEntry);
while ((m_currentEntry == nullptr) while ((m_currentEntry == nullptr) ||
|| (m_currentRecord = m_currentEntry->m_dataList[m_currentRecordIndex].Load()) == nullptr) (m_currentRecord =
{ m_currentEntry->m_dataList[m_currentRecordIndex].Load()) ==
if (m_currentEntry == nullptr) nullptr) {
{ if (m_currentEntry == nullptr) {
++m_currentBucketIndex; ++m_currentBucketIndex;
m_currentRecordIndex = 0U; m_currentRecordIndex = 0U;
if (IsEnd()) if (IsEnd()) {
{
return false; return false;
} }
m_currentEntry = &m_hashTable.m_buckets[m_currentBucketIndex]; m_currentEntry = &m_hashTable.m_buckets[m_currentBucketIndex];
} } else {
else
{
MoveToNextData(); MoveToNextData();
} }
} }
@ -191,20 +168,16 @@ public:
return true; return true;
} }
Key GetKey() const override Key GetKey() const override {
{ if (!IsValid()) {
if (!IsValid())
{
throw RuntimeException("HashTableIterator is not correctly used."); throw RuntimeException("HashTableIterator is not correctly used.");
} }
return m_recordSerializer.Deserialize(*m_currentRecord).m_key; return m_recordSerializer.Deserialize(*m_currentRecord).m_key;
} }
Value GetValue() const override Value GetValue() const override {
{ if (!IsValid()) {
if (!IsValid())
{
throw RuntimeException("HashTableIterator is not correctly used."); throw RuntimeException("HashTableIterator is not correctly used.");
} }
@ -214,23 +187,19 @@ public:
Iterator(const Iterator&) = delete; Iterator(const Iterator&) = delete;
Iterator& operator=(const Iterator&) = delete; Iterator& operator=(const Iterator&) = delete;
private: private:
bool IsValid() const bool IsValid() const {
{ return !IsEnd() && (m_currentEntry != nullptr) &&
return !IsEnd() (m_currentRecord != nullptr);
&& (m_currentEntry != nullptr)
&& (m_currentRecord != nullptr);
} }
bool IsEnd() const bool IsEnd() const {
{ return m_currentBucketIndex ==
return m_currentBucketIndex == static_cast<std::int64_t>(m_hashTable.m_buckets.size()); static_cast<std::int64_t>(m_hashTable.m_buckets.size());
} }
void MoveToNextData() void MoveToNextData() {
{ if (++m_currentRecordIndex >= HashTable::Entry::c_numDataPerEntry) {
if (++m_currentRecordIndex >= HashTable::Entry::c_numDataPerEntry)
{
m_currentRecordIndex = 0U; m_currentRecordIndex = 0U;
m_currentEntry = m_currentEntry->m_next.Load(); m_currentEntry = m_currentEntry->m_next.Load();
} }
@ -246,60 +215,48 @@ private:
const RecordBuffer* m_currentRecord; const RecordBuffer* m_currentRecord;
}; };
// The following warning is from the virtual inheritance and safe to disable in
// The following warning is from the virtual inheritance and safe to disable in this case. // this case. https://msdn.microsoft.com/en-us/library/6b3sy7ae.aspx
// https://msdn.microsoft.com/en-us/library/6b3sy7ae.aspx
#pragma warning(push) #pragma warning(push)
#pragma warning(disable:4250) #pragma warning(disable : 4250)
// WritableHashTable class implements IWritableHashTable interface and also provides // WritableHashTable class implements IWritableHashTable interface and also
// the read only access (Get()) to the hash table. // provides the read only access (Get()) to the hash table. Note the virtual
// Note the virtual inheritance on ReadOnlyHashTable<Allocator> so that any derived class // inheritance on ReadOnlyHashTable<Allocator> so that any derived class can
// can have only one ReadOnlyHashTable base class instance. // have only one ReadOnlyHashTable base class instance.
template <typename Allocator> template <typename Allocator>
class WritableHashTable class WritableHashTable : public virtual ReadOnlyHashTable<Allocator>,
: public virtual ReadOnlyHashTable<Allocator> public IWritableHashTable {
, public IWritableHashTable public:
{
public:
using Base = ReadOnlyHashTable<Allocator>; using Base = ReadOnlyHashTable<Allocator>;
using HashTable = typename Base::HashTable; using HashTable = typename Base::HashTable;
WritableHashTable( WritableHashTable(HashTable& hashTable, IEpochActionManager& epochManager)
HashTable& hashTable, : Base(hashTable), m_epochManager{epochManager} {}
IEpochActionManager& epochManager)
: Base(hashTable)
, m_epochManager{ epochManager }
{}
virtual void Add(const Key& key, const Value& value) override virtual void Add(const Key& key, const Value& value) override {
{
Add(CreateRecordBuffer(key, value)); Add(CreateRecordBuffer(key, value));
} }
virtual bool Remove(const Key& key) override virtual bool Remove(const Key& key) override {
{
const auto bucketInfo = this->GetBucketInfo(key); const auto bucketInfo = this->GetBucketInfo(key);
auto* entry = &(this->m_hashTable.m_buckets[bucketInfo.first]); auto* entry = &(this->m_hashTable.m_buckets[bucketInfo.first]);
typename HashTable::Lock lock{ this->m_hashTable.GetMutex(bucketInfo.first) }; typename HashTable::Lock lock{this->m_hashTable.GetMutex(bucketInfo.first)};
// Note that similar to Add(), the following block is performed inside a critical section, // Note that similar to Add(), the following block is performed inside a
// therefore, it is safe to do "Load"s with memory_order_relaxed. // critical section, therefore, it is safe to do "Load"s with
while (entry != nullptr) // memory_order_relaxed.
{ while (entry != nullptr) {
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) {
{ if (bucketInfo.second == entry->m_tags[i]) {
if (bucketInfo.second == entry->m_tags[i]) const auto data =
{ entry->m_dataList[i].Load(std::memory_order_relaxed);
const auto data = entry->m_dataList[i].Load(std::memory_order_relaxed);
if (data != nullptr) if (data != nullptr) {
{
const auto record = this->m_recordSerializer.Deserialize(*data); const auto record = this->m_recordSerializer.Deserialize(*data);
if (record.m_key == key) if (record.m_key == key) {
{
Remove(*entry, i); Remove(*entry, i);
return true; return true;
} }
@ -313,21 +270,19 @@ public:
return false; return false;
} }
virtual ISerializerPtr GetSerializer() const override virtual ISerializerPtr GetSerializer() const override {
{
return std::make_unique<WritableHashTable::Serializer>(this->m_hashTable); return std::make_unique<WritableHashTable::Serializer>(this->m_hashTable);
} }
protected: protected:
void Add(RecordBuffer* recordToAdd) void Add(RecordBuffer* recordToAdd) {
{
assert(recordToAdd != nullptr); assert(recordToAdd != nullptr);
const auto newRecord = this->m_recordSerializer.Deserialize(*recordToAdd); const auto newRecord = this->m_recordSerializer.Deserialize(*recordToAdd);
const auto& newKey = newRecord.m_key; const auto& newKey = newRecord.m_key;
const auto& newValue = newRecord.m_value; const auto& newValue = newRecord.m_value;
Stat stat{ newKey.m_size, newValue.m_size }; Stat stat{newKey.m_size, newValue.m_size};
const auto bucketInfo = this->GetBucketInfo(newKey); const auto bucketInfo = this->GetBucketInfo(newKey);
@ -336,33 +291,28 @@ protected:
typename HashTable::Entry* entryToUpdate = nullptr; typename HashTable::Entry* entryToUpdate = nullptr;
std::uint8_t curDataIndex = 0U; std::uint8_t curDataIndex = 0U;
typename HashTable::UniqueLock lock{ this->m_hashTable.GetMutex(bucketInfo.first) }; typename HashTable::UniqueLock lock{
this->m_hashTable.GetMutex(bucketInfo.first)};
// Note that the following block is performed inside a critical section, therefore, // Note that the following block is performed inside a critical section,
// it is safe to do "Load"s with memory_order_relaxed. // therefore, it is safe to do "Load"s with memory_order_relaxed.
while (curEntry != nullptr) while (curEntry != nullptr) {
{
++stat.m_chainIndex; ++stat.m_chainIndex;
for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) for (std::uint8_t i = 0; i < HashTable::Entry::c_numDataPerEntry; ++i) {
{ const auto data =
const auto data = curEntry->m_dataList[i].Load(std::memory_order_relaxed); curEntry->m_dataList[i].Load(std::memory_order_relaxed);
if (data == nullptr) if (data == nullptr) {
{ if (entryToUpdate == nullptr) {
if (entryToUpdate == nullptr) // Found an entry with no data set, but still need to go through the
{ // end of the list to see if an entry with the given key exists.
// Found an entry with no data set, but still need to go through the end of
// the list to see if an entry with the given key exists.
entryToUpdate = curEntry; entryToUpdate = curEntry;
curDataIndex = i; curDataIndex = i;
} }
} } else if (curEntry->m_tags[i] == bucketInfo.second) {
else if (curEntry->m_tags[i] == bucketInfo.second)
{
const auto oldRecord = this->m_recordSerializer.Deserialize(*data); const auto oldRecord = this->m_recordSerializer.Deserialize(*data);
if (newKey == oldRecord.m_key) if (newKey == oldRecord.m_key) {
{
// Will overwrite this entry data. // Will overwrite this entry data.
entryToUpdate = curEntry; entryToUpdate = curEntry;
curDataIndex = i; curDataIndex = i;
@ -373,19 +323,19 @@ protected:
} }
// Found the entry data to replaces. // Found the entry data to replaces.
if (stat.m_oldValueSize != 0U) if (stat.m_oldValueSize != 0U) {
{
break; break;
} }
// Check if this is the end of the chaining. If so, create a new entry if we haven't found // Check if this is the end of the chaining. If so, create a new entry if
// any entry to update along the way. // we haven't found any entry to update along the way.
if (entryToUpdate == nullptr && curEntry->m_next.Load(std::memory_order_relaxed) == nullptr) if (entryToUpdate == nullptr &&
{ curEntry->m_next.Load(std::memory_order_relaxed) == nullptr) {
curEntry->m_next.Store( curEntry->m_next.Store(
new (Detail::to_raw_pointer( new (Detail::to_raw_pointer(
this->m_hashTable.template GetAllocator<typename HashTable::Entry>().allocate(1U))) this->m_hashTable
typename HashTable::Entry(), .template GetAllocator<typename HashTable::Entry>()
.allocate(1U))) typename HashTable::Entry(),
std::memory_order_release); std::memory_order_release);
stat.m_isNewEntryAdded = true; stat.m_isNewEntryAdded = true;
@ -396,7 +346,8 @@ protected:
assert(entryToUpdate != nullptr); assert(entryToUpdate != nullptr);
auto recordToDelete = UpdateRecord(*entryToUpdate, curDataIndex, recordToAdd, bucketInfo.second); auto recordToDelete = UpdateRecord(*entryToUpdate, curDataIndex,
recordToAdd, bucketInfo.second);
lock.unlock(); lock.unlock();
@ -405,10 +356,9 @@ protected:
ReleaseRecord(recordToDelete); ReleaseRecord(recordToDelete);
} }
// The chainIndex is the 1-based index for the given entry in the chained bucket list. // The chainIndex is the 1-based index for the given entry in the chained
// It is assumed that this function is called under a lock. // bucket list. It is assumed that this function is called under a lock.
void Remove(typename HashTable::Entry& entry, std::uint8_t index) void Remove(typename HashTable::Entry& entry, std::uint8_t index) {
{
auto recordToDelete = UpdateRecord(entry, index, nullptr, 0U); auto recordToDelete = UpdateRecord(entry, index, nullptr, 0U);
assert(recordToDelete != nullptr); assert(recordToDelete != nullptr);
@ -416,36 +366,32 @@ protected:
const auto record = this->m_recordSerializer.Deserialize(*recordToDelete); const auto record = this->m_recordSerializer.Deserialize(*recordToDelete);
UpdatePerfDataForRemove( UpdatePerfDataForRemove(
Stat{ Stat{record.m_key.m_size, record.m_value.m_size, 0U});
record.m_key.m_size,
record.m_value.m_size,
0U
});
ReleaseRecord(recordToDelete); ReleaseRecord(recordToDelete);
} }
private: private:
struct Stat; struct Stat;
class Serializer; class Serializer;
RecordBuffer* CreateRecordBuffer(const Key& key, const Value& value) RecordBuffer* CreateRecordBuffer(const Key& key, const Value& value) {
{ const auto bufferSize =
const auto bufferSize = this->m_recordSerializer.CalculateBufferSize(key, value); this->m_recordSerializer.CalculateBufferSize(key, value);
auto buffer = Detail::to_raw_pointer( auto buffer = Detail::to_raw_pointer(
this->m_hashTable.template GetAllocator<std::uint8_t>().allocate(bufferSize)); this->m_hashTable.template GetAllocator<std::uint8_t>().allocate(
bufferSize));
return this->m_recordSerializer.Serialize(key, value, buffer, bufferSize); return this->m_recordSerializer.Serialize(key, value, buffer, bufferSize);
} }
RecordBuffer* UpdateRecord( RecordBuffer* UpdateRecord(typename HashTable::Entry& entry,
typename HashTable::Entry& entry,
std::uint8_t index, std::uint8_t index,
RecordBuffer* newRecord, RecordBuffer* newRecord,
std::uint8_t newTag) std::uint8_t newTag) {
{ // This function should be called under a lock, so calling with
// This function should be called under a lock, so calling with memory_order_relaxed for Load() is safe. // memory_order_relaxed for Load() is safe.
auto& recordHolder = entry.m_dataList[index]; auto& recordHolder = entry.m_dataList[index];
auto oldRecord = recordHolder.Load(std::memory_order_relaxed); auto oldRecord = recordHolder.Load(std::memory_order_relaxed);
@ -455,54 +401,49 @@ private:
return oldRecord; return oldRecord;
} }
void ReleaseRecord(RecordBuffer* record) void ReleaseRecord(RecordBuffer* record) {
{ if (record == nullptr) {
if (record == nullptr)
{
return; return;
} }
m_epochManager.RegisterAction( m_epochManager.RegisterAction([this, record]() {
[this, record]()
{
record->~RecordBuffer(); record->~RecordBuffer();
this->m_hashTable.template GetAllocator<RecordBuffer>().deallocate(record, 1U); this->m_hashTable.template GetAllocator<RecordBuffer>().deallocate(record,
1U);
}); });
} }
void UpdatePerfDataForAdd(const Stat& stat) void UpdatePerfDataForAdd(const Stat& stat) {
{
auto& perfData = this->m_hashTable.m_perfData; auto& perfData = this->m_hashTable.m_perfData;
if (stat.m_oldValueSize != 0U) if (stat.m_oldValueSize != 0U) {
{
// Updating the existing record. Therefore, no change in the key size. // Updating the existing record. Therefore, no change in the key size.
perfData.Add(HashTablePerfCounter::TotalValueSize, perfData.Add(HashTablePerfCounter::TotalValueSize,
static_cast<HashTablePerfData::TValue>(stat.m_valueSize) - stat.m_oldValueSize); static_cast<HashTablePerfData::TValue>(stat.m_valueSize) -
} stat.m_oldValueSize);
else } else {
{
// We are adding a new data instead of replacing. // We are adding a new data instead of replacing.
perfData.Add(HashTablePerfCounter::TotalKeySize, stat.m_keySize); perfData.Add(HashTablePerfCounter::TotalKeySize, stat.m_keySize);
perfData.Add(HashTablePerfCounter::TotalValueSize, stat.m_valueSize); perfData.Add(HashTablePerfCounter::TotalValueSize, stat.m_valueSize);
perfData.Add(HashTablePerfCounter::TotalIndexSize, perfData.Add(
HashTablePerfCounter::TotalIndexSize,
// Record overhead. // Record overhead.
this->m_recordSerializer.CalculateRecordOverhead() this->m_recordSerializer.CalculateRecordOverhead()
// Entry overhead if created. // Entry overhead if created.
+ (stat.m_isNewEntryAdded ? sizeof(typename HashTable::Entry) : 0U)); + (stat.m_isNewEntryAdded ? sizeof(typename HashTable::Entry)
: 0U));
perfData.Min(HashTablePerfCounter::MinKeySize, stat.m_keySize); perfData.Min(HashTablePerfCounter::MinKeySize, stat.m_keySize);
perfData.Max(HashTablePerfCounter::MaxKeySize, stat.m_keySize); perfData.Max(HashTablePerfCounter::MaxKeySize, stat.m_keySize);
perfData.Increment(HashTablePerfCounter::RecordsCount); perfData.Increment(HashTablePerfCounter::RecordsCount);
if (stat.m_isNewEntryAdded) if (stat.m_isNewEntryAdded) {
{
perfData.Increment(HashTablePerfCounter::ChainingEntriesCount); perfData.Increment(HashTablePerfCounter::ChainingEntriesCount);
if (stat.m_chainIndex > 1U) if (stat.m_chainIndex > 1U) {
{ perfData.Max(HashTablePerfCounter::MaxBucketChainLength,
perfData.Max(HashTablePerfCounter::MaxBucketChainLength, stat.m_chainIndex); stat.m_chainIndex);
} }
} }
} }
@ -511,14 +452,14 @@ private:
perfData.Max(HashTablePerfCounter::MaxValueSize, stat.m_valueSize); perfData.Max(HashTablePerfCounter::MaxValueSize, stat.m_valueSize);
} }
void UpdatePerfDataForRemove(const Stat& stat) void UpdatePerfDataForRemove(const Stat& stat) {
{
auto& perfData = this->m_hashTable.m_perfData; auto& perfData = this->m_hashTable.m_perfData;
perfData.Decrement(HashTablePerfCounter::RecordsCount); perfData.Decrement(HashTablePerfCounter::RecordsCount);
perfData.Subtract(HashTablePerfCounter::TotalKeySize, stat.m_keySize); perfData.Subtract(HashTablePerfCounter::TotalKeySize, stat.m_keySize);
perfData.Subtract(HashTablePerfCounter::TotalValueSize, stat.m_valueSize); perfData.Subtract(HashTablePerfCounter::TotalValueSize, stat.m_valueSize);
perfData.Subtract(HashTablePerfCounter::TotalIndexSize, this->m_recordSerializer.CalculateRecordOverhead()); perfData.Subtract(HashTablePerfCounter::TotalIndexSize,
this->m_recordSerializer.CalculateRecordOverhead());
} }
IEpochActionManager& m_epochManager; IEpochActionManager& m_epochManager;
@ -526,26 +467,22 @@ private:
#pragma warning(pop) #pragma warning(pop)
// WritableHashTable::Stat struct encapsulates stats for Add()/Remove(). // WritableHashTable::Stat struct encapsulates stats for Add()/Remove().
template <typename Allocator> template <typename Allocator>
struct WritableHashTable<Allocator>::Stat struct WritableHashTable<Allocator>::Stat {
{
using KeySize = Key::size_type; using KeySize = Key::size_type;
using ValueSize = Value::size_type; using ValueSize = Value::size_type;
explicit Stat( explicit Stat(KeySize keySize = 0U,
KeySize keySize = 0U,
ValueSize valueSize = 0U, ValueSize valueSize = 0U,
ValueSize oldValueSize = 0U, ValueSize oldValueSize = 0U,
std::uint32_t chainIndex = 0U, std::uint32_t chainIndex = 0U,
bool isNewEntryAdded = false) bool isNewEntryAdded = false)
: m_keySize{ keySize } : m_keySize{keySize},
, m_valueSize{ valueSize } m_valueSize{valueSize},
, m_oldValueSize{ oldValueSize } m_oldValueSize{oldValueSize},
, m_chainIndex{ chainIndex } m_chainIndex{chainIndex},
, m_isNewEntryAdded{ isNewEntryAdded } m_isNewEntryAdded{isNewEntryAdded} {}
{}
KeySize m_keySize; KeySize m_keySize;
ValueSize m_valueSize; ValueSize m_valueSize;
@ -554,29 +491,24 @@ struct WritableHashTable<Allocator>::Stat
bool m_isNewEntryAdded; bool m_isNewEntryAdded;
}; };
// WritableHashTable::Serializer class that implements ISerializer, which
// WritableHashTable::Serializer class that implements ISerializer, which provides // provides the functionality to serialize the WritableHashTable.
// the functionality to serialize the WritableHashTable.
template <typename Allocator> template <typename Allocator>
class WritableHashTable<Allocator>::Serializer : public IWritableHashTable::ISerializer class WritableHashTable<Allocator>::Serializer
{ : public IWritableHashTable::ISerializer {
public: public:
explicit Serializer(HashTable& hashTable) explicit Serializer(HashTable& hashTable) : m_hashTable{hashTable} {}
: m_hashTable{ hashTable }
{}
Serializer(const Serializer&) = delete; Serializer(const Serializer&) = delete;
Serializer& operator=(const Serializer&) = delete; Serializer& operator=(const Serializer&) = delete;
void Serialize( void Serialize(std::ostream& stream,
std::ostream& stream, const Utils::Properties& /* properties */) override {
const Utils::Properties& /* properties */) override ReadWrite::Serializer<HashTable, ReadWrite::ReadOnlyHashTable>{}.Serialize(
{ m_hashTable, stream);
ReadWrite::Serializer<
HashTable, ReadWrite::ReadOnlyHashTable>{}.Serialize(m_hashTable, stream);
} }
private: private:
HashTable& m_hashTable; HashTable& m_hashTable;
}; };

Просмотреть файл

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <cstdint>
#include <boost/format.hpp> #include <boost/format.hpp>
#include <cstdint>
#include <iosfwd> #include <iosfwd>
#include "Epoch/IEpochActionManager.h" #include "Epoch/IEpochActionManager.h"
#include "Log/PerfCounter.h" #include "Log/PerfCounter.h"
@ -9,27 +9,21 @@
#include "Utils/Exception.h" #include "Utils/Exception.h"
#include "Utils/Properties.h" #include "Utils/Properties.h"
namespace L4 namespace L4 {
{ namespace HashTable {
namespace HashTable namespace ReadWrite {
{
namespace ReadWrite
{
// Note that the HashTable template parameter in this file is // Note that the HashTable template parameter in this file is
// HashTable::ReadWrite::ReadOnlyHashTable<Allocator>::HashTable. // HashTable::ReadWrite::ReadOnlyHashTable<Allocator>::HashTable.
// However, due to the cyclic dependency, it needs to be passed as a template type. // However, due to the cyclic dependency, it needs to be passed as a template
// type.
// All the deprecated (previous versions) serializer should be put inside the
// Deprecated namespace. Removing any of the Deprecated serializers from the
// source code will require the major package version change.
namespace Deprecated {} // namespace Deprecated
// All the deprecated (previous versions) serializer should be put inside the Deprecated namespace. namespace Current {
// Removing any of the Deprecated serializers from the source code will require the major package version change.
namespace Deprecated
{
} // namespace Deprecated
namespace Current
{
constexpr std::uint8_t c_version = 1U; constexpr std::uint8_t c_version = 1U;
@ -40,18 +34,14 @@ constexpr std::uint8_t c_version = 1U;
// <Key size> <Key bytes> <Value size> <Value bytes> // <Key size> <Key bytes> <Value size> <Value bytes>
// Otherwise, end of the records. // Otherwise, end of the records.
template <typename HashTable, template <typename> class ReadOnlyHashTable> template <typename HashTable, template <typename> class ReadOnlyHashTable>
class Serializer class Serializer {
{ public:
public:
Serializer() = default; Serializer() = default;
Serializer(const Serializer&) = delete; Serializer(const Serializer&) = delete;
Serializer& operator=(const Serializer&) = delete; Serializer& operator=(const Serializer&) = delete;
void Serialize( void Serialize(HashTable& hashTable, std::ostream& stream) const {
HashTable& hashTable,
std::ostream& stream) const
{
auto& perfData = hashTable.m_perfData; auto& perfData = hashTable.m_perfData;
perfData.Set(HashTablePerfCounter::RecordsCountSavedFromSerializer, 0); perfData.Set(HashTablePerfCounter::RecordsCountSavedFromSerializer, 0);
@ -61,11 +51,11 @@ public:
helper.Serialize(&hashTable.m_setting, sizeof(hashTable.m_setting)); helper.Serialize(&hashTable.m_setting, sizeof(hashTable.m_setting));
ReadOnlyHashTable<typename HashTable::Allocator> readOnlyHashTable(hashTable); ReadOnlyHashTable<typename HashTable::Allocator> readOnlyHashTable(
hashTable);
auto iterator = readOnlyHashTable.GetIterator(); auto iterator = readOnlyHashTable.GetIterator();
while (iterator->MoveNext()) while (iterator->MoveNext()) {
{
helper.Serialize(true); // Indicates record exists. helper.Serialize(true); // Indicates record exists.
const auto key = iterator->GetKey(); const auto key = iterator->GetKey();
const auto value = iterator->GetValue(); const auto value = iterator->GetValue();
@ -81,40 +71,39 @@ public:
helper.Serialize(false); // Indicates the end of records. helper.Serialize(false); // Indicates the end of records.
// Flush perf counter so that the values are up to date when GetPerfData() is called. // Flush perf counter so that the values are up to date when GetPerfData()
// is called.
std::atomic_thread_fence(std::memory_order_release); std::atomic_thread_fence(std::memory_order_release);
} }
}; };
// Current Deserializer used for deserializing hash tables. // Current Deserializer used for deserializing hash tables.
template <typename Memory, typename HashTable, template <typename> class WritableHashTable> template <typename Memory,
class Deserializer typename HashTable,
{ template <typename>
public: class WritableHashTable>
explicit Deserializer(const Utils::Properties& /* properties */) class Deserializer {
{} public:
explicit Deserializer(const Utils::Properties& /* properties */) {}
Deserializer(const Deserializer&) = delete; Deserializer(const Deserializer&) = delete;
Deserializer& operator=(const Deserializer&) = delete; Deserializer& operator=(const Deserializer&) = delete;
typename Memory::template UniquePtr<HashTable> Deserialize( typename Memory::template UniquePtr<HashTable> Deserialize(
Memory& memory, Memory& memory,
std::istream& stream) const std::istream& stream) const {
{
DeserializerHelper helper(stream); DeserializerHelper helper(stream);
typename HashTable::Setting setting; typename HashTable::Setting setting;
helper.Deserialize(setting); helper.Deserialize(setting);
auto hashTable{ memory.template MakeUnique<HashTable>( auto hashTable{
setting, memory.template MakeUnique<HashTable>(setting, memory.GetAllocator())};
memory.GetAllocator()) };
EpochActionManager epochActionManager; EpochActionManager epochActionManager;
WritableHashTable<typename HashTable::Allocator> writableHashTable( WritableHashTable<typename HashTable::Allocator> writableHashTable(
*hashTable, *hashTable, epochActionManager);
epochActionManager);
auto& perfData = hashTable->m_perfData; auto& perfData = hashTable->m_perfData;
@ -124,8 +113,7 @@ public:
bool hasMoreData = false; bool hasMoreData = false;
helper.Deserialize(hasMoreData); helper.Deserialize(hasMoreData);
while (hasMoreData) while (hasMoreData) {
{
IReadOnlyHashTable::Key key; IReadOnlyHashTable::Key key;
IReadOnlyHashTable::Value value; IReadOnlyHashTable::Value value;
@ -143,74 +131,77 @@ public:
helper.Deserialize(hasMoreData); helper.Deserialize(hasMoreData);
perfData.Increment(HashTablePerfCounter::RecordsCountLoadedFromSerializer); perfData.Increment(
HashTablePerfCounter::RecordsCountLoadedFromSerializer);
} }
// Flush perf counter so that the values are up to date when GetPerfData() is called. // Flush perf counter so that the values are up to date when GetPerfData()
// is called.
std::atomic_thread_fence(std::memory_order_release); std::atomic_thread_fence(std::memory_order_release);
return hashTable; return hashTable;
} }
private: private:
// Deserializer internally uses WritableHashTable for deserialization, therefore // Deserializer internally uses WritableHashTable for deserialization,
// an implementation of IEpochActionManager is needed. Since all the keys in the hash table // therefore an implementation of IEpochActionManager is needed. Since all the
// are expected to be unique, no RegisterAction() should be called. // keys in the hash table are expected to be unique, no RegisterAction()
class EpochActionManager : public IEpochActionManager // should be called.
{ class EpochActionManager : public IEpochActionManager {
public: public:
void RegisterAction(Action&& /* action */) override void RegisterAction(Action&& /* action */) override {
{ // Since it is assumed that the serializer is loading from the stream
// Since it is assumed that the serializer is loading from the stream generated by the same serializer, // generated by the same serializer, it is guaranteed that all the keys
// it is guaranteed that all the keys are unique (a property of a hash table). Therefore, RegisterAction() // are unique (a property of a hash table). Therefore, RegisterAction()
// should not be called by the WritableHashTable. // should not be called by the WritableHashTable.
throw RuntimeException("RegisterAction() should not be called from the serializer."); throw RuntimeException(
"RegisterAction() should not be called from the serializer.");
} }
}; };
}; };
} // namespace Current } // namespace Current
// Serializer is the main driver for serializing a hash table. // Serializer is the main driver for serializing a hash table.
// It always uses the Current::Serializer for serializing a hash table. // It always uses the Current::Serializer for serializing a hash table.
template <typename HashTable, template <typename> class ReadOnlyHashTable> template <typename HashTable, template <typename> class ReadOnlyHashTable>
class Serializer class Serializer {
{ public:
public:
Serializer() = default; Serializer() = default;
Serializer(const Serializer&) = delete; Serializer(const Serializer&) = delete;
Serializer& operator=(const Serializer&) = delete; Serializer& operator=(const Serializer&) = delete;
void Serialize(HashTable& hashTable, std::ostream& stream) const void Serialize(HashTable& hashTable, std::ostream& stream) const {
{ Current::Serializer<HashTable, ReadOnlyHashTable>{}.Serialize(hashTable,
Current::Serializer<HashTable, ReadOnlyHashTable>{}.Serialize(hashTable, stream); stream);
} }
}; };
// Deserializer is the main driver for deserializing the input stream to create a hash table. // Deserializer is the main driver for deserializing the input stream to create
template <typename Memory, typename HashTable, template <typename> class WritableHashTable> // a hash table.
class Deserializer template <typename Memory,
{ typename HashTable,
public: template <typename>
class WritableHashTable>
class Deserializer {
public:
explicit Deserializer(const Utils::Properties& properties) explicit Deserializer(const Utils::Properties& properties)
: m_properties(properties) : m_properties(properties) {}
{}
Deserializer(const Deserializer&) = delete; Deserializer(const Deserializer&) = delete;
Deserializer& operator=(const Deserializer&) = delete; Deserializer& operator=(const Deserializer&) = delete;
typename Memory::template UniquePtr<HashTable> Deserialize( typename Memory::template UniquePtr<HashTable> Deserialize(
Memory& memory, Memory& memory,
std::istream& stream) const std::istream& stream) const {
{
std::uint8_t version = 0U; std::uint8_t version = 0U;
DeserializerHelper(stream).Deserialize(version); DeserializerHelper(stream).Deserialize(version);
switch (version) switch (version) {
{
case Current::c_version: case Current::c_version:
return Current::Deserializer<Memory, HashTable, WritableHashTable>{ m_properties }.Deserialize(memory, stream); return Current::Deserializer<Memory, HashTable, WritableHashTable>{
m_properties}
.Deserialize(memory, stream);
default: default:
boost::format err("Unsupported version '%1%' is given."); boost::format err("Unsupported version '%1%' is given.");
err % version; err % version;
@ -218,11 +209,10 @@ public:
} }
} }
private: private:
const Utils::Properties& m_properties; const Utils::Properties& m_properties;
}; };
} // namespace ReadWrite } // namespace ReadWrite
} // namespace HashTable } // namespace HashTable
} // namespace L4 } // namespace L4

Просмотреть файл

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -8,12 +9,9 @@
#include "Interprocess/Connection/EndPointInfo.h" #include "Interprocess/Connection/EndPointInfo.h"
#include "Interprocess/Utils/Handle.h" #include "Interprocess/Utils/Handle.h"
namespace L4 namespace L4 {
{ namespace Interprocess {
namespace Interprocess namespace Connection {
{
namespace Connection
{
// ConnectionMonitor monitors any registered end points. // ConnectionMonitor monitors any registered end points.
// ConnectionMonitor creates a kernel event for local end point, // ConnectionMonitor creates a kernel event for local end point,
@ -22,9 +20,8 @@ namespace Connection
// is closed, the callback registered is triggered and the remote endpoint // is closed, the callback registered is triggered and the remote endpoint
// is removed from the ConnectionMonitor after the callback is finished.. // is removed from the ConnectionMonitor after the callback is finished..
class ConnectionMonitor class ConnectionMonitor
: public std::enable_shared_from_this<ConnectionMonitor> : public std::enable_shared_from_this<ConnectionMonitor> {
{ public:
public:
using Callback = std::function<void(const EndPointInfo&)>; using Callback = std::function<void(const EndPointInfo&)>;
ConnectionMonitor(); ConnectionMonitor();
@ -41,7 +38,7 @@ public:
ConnectionMonitor(const ConnectionMonitor&) = delete; ConnectionMonitor(const ConnectionMonitor&) = delete;
ConnectionMonitor& operator=(const ConnectionMonitor&) = delete; ConnectionMonitor& operator=(const ConnectionMonitor&) = delete;
private: private:
class HandleMonitor; class HandleMonitor;
// UnRegister() removes the unregistered end points from m_remoteEvents. // UnRegister() removes the unregistered end points from m_remoteEvents.
@ -51,7 +48,8 @@ private:
Utils::Handle m_localEvent; Utils::Handle m_localEvent;
mutable std::map<EndPointInfo, std::unique_ptr<HandleMonitor>> m_remoteMonitors; mutable std::map<EndPointInfo, std::unique_ptr<HandleMonitor>>
m_remoteMonitors;
mutable std::mutex m_mutexOnRemoteMonitors; mutable std::mutex m_mutexOnRemoteMonitors;
@ -60,32 +58,26 @@ private:
mutable std::mutex m_mutexOnUnregisteredEndPoints; mutable std::mutex m_mutexOnUnregisteredEndPoints;
}; };
// ConnectionMonitor::HandleMonitor opens the given endpoint's process // ConnectionMonitor::HandleMonitor opens the given endpoint's process
// and event handles and waits for any event triggers. // and event handles and waits for any event triggers.
class ConnectionMonitor::HandleMonitor class ConnectionMonitor::HandleMonitor {
{ public:
public: HandleMonitor(const EndPointInfo& remoteEndPoint, Callback callback);
HandleMonitor(
const EndPointInfo& remoteEndPoint,
Callback callback);
HandleMonitor(const HandleMonitor&) = delete; HandleMonitor(const HandleMonitor&) = delete;
HandleMonitor& operator=(const HandleMonitor&) = delete; HandleMonitor& operator=(const HandleMonitor&) = delete;
private: private:
class Waiter; class Waiter;
std::unique_ptr<Waiter> m_eventWaiter; std::unique_ptr<Waiter> m_eventWaiter;
std::unique_ptr<Waiter> m_processWaiter; std::unique_ptr<Waiter> m_processWaiter;
}; };
// ConnectionMonitor::HandleMonitor::Waiter waits on the given handle and calls // ConnectionMonitor::HandleMonitor::Waiter waits on the given handle and calls
// the given callback when an event is triggered on the handle. // the given callback when an event is triggered on the handle.
class ConnectionMonitor::HandleMonitor::Waiter class ConnectionMonitor::HandleMonitor::Waiter {
{ public:
public:
using Callback = std::function<void()>; using Callback = std::function<void()>;
Waiter(Utils::Handle handle, Callback callback); Waiter(Utils::Handle handle, Callback callback);
@ -95,9 +87,8 @@ public:
Waiter(const Waiter&) = delete; Waiter(const Waiter&) = delete;
Waiter& operator=(const Waiter&) = delete; Waiter& operator=(const Waiter&) = delete;
private: private:
static VOID CALLBACK OnEvent( static VOID CALLBACK OnEvent(PTP_CALLBACK_INSTANCE instance,
PTP_CALLBACK_INSTANCE instance,
PVOID context, PVOID context,
PTP_WAIT wait, PTP_WAIT wait,
TP_WAIT_RESULT waitResult); TP_WAIT_RESULT waitResult);

Просмотреть файл

@ -1,34 +1,24 @@
#pragma once #pragma once
#include <cstdint>
#include <boost/uuid/uuid.hpp> #include <boost/uuid/uuid.hpp>
#include <cstdint>
namespace L4 namespace L4 {
{ namespace Interprocess {
namespace Interprocess namespace Connection {
{
namespace Connection
{
// EndPointInfo struct encapsulates the connection end point // EndPointInfo struct encapsulates the connection end point
// information across process boundaries. // information across process boundaries.
struct EndPointInfo struct EndPointInfo {
{ explicit EndPointInfo(std::uint32_t pid = 0U,
explicit EndPointInfo(
std::uint32_t pid = 0U,
const boost::uuids::uuid& uuid = {}) const boost::uuids::uuid& uuid = {})
: m_pid{ pid } : m_pid{pid}, m_uuid{uuid} {}
, m_uuid{ uuid }
{}
bool operator==(const EndPointInfo& other) const bool operator==(const EndPointInfo& other) const {
{ return (m_pid == other.m_pid) && (m_uuid == other.m_uuid);
return (m_pid == other.m_pid)
&& (m_uuid == other.m_uuid);
} }
bool operator<(const EndPointInfo& other) const bool operator<(const EndPointInfo& other) const {
{
return m_uuid < other.m_uuid; return m_uuid < other.m_uuid;
} }

Просмотреть файл

@ -3,26 +3,20 @@
#include <string> #include <string>
#include "Interprocess/Connection/EndPointInfo.h" #include "Interprocess/Connection/EndPointInfo.h"
namespace L4 namespace L4 {
{ namespace Interprocess {
namespace Interprocess namespace Connection {
{
namespace Connection
{
// EndPointInfoFactory creates an EndPointInfo object with the current // EndPointInfoFactory creates an EndPointInfo object with the current
// process id and a random uuid. // process id and a random uuid.
class EndPointInfoFactory class EndPointInfoFactory {
{ public:
public:
EndPointInfo Create() const; EndPointInfo Create() const;
}; };
// StringConverter provides a functionality to convert EndPointInfo to a string. // StringConverter provides a functionality to convert EndPointInfo to a string.
class StringConverter class StringConverter {
{ public:
public:
std::string operator()(const EndPointInfo& endPoint) const; std::string operator()(const EndPointInfo& endPoint) const;
}; };

Просмотреть файл

@ -2,18 +2,13 @@
#include <boost/interprocess/containers/list.hpp> #include <boost/interprocess/containers/list.hpp>
namespace L4 namespace L4 {
{ namespace Interprocess {
namespace Interprocess namespace Container {
{
namespace Container
{
template <typename T, typename Allocator> template <typename T, typename Allocator>
using List = boost::interprocess::list<T, Allocator>; using List = boost::interprocess::list<T, Allocator>;
} // namespace Container } // namespace Container
} // namespace Interprocess } // namespace Interprocess
} // namespace L4 } // namespace L4

Просмотреть файл

@ -2,17 +2,13 @@
#include <boost/interprocess/containers/string.hpp> #include <boost/interprocess/containers/string.hpp>
namespace L4 namespace L4 {
{ namespace Interprocess {
namespace Interprocess namespace Container {
{
namespace Container
{
template <typename Allocator> template <typename Allocator>
using String = boost::interprocess::basic_string<char, std::char_traits<char>, Allocator>; using String =
boost::interprocess::basic_string<char, std::char_traits<char>, Allocator>;
} // namespace Container } // namespace Container
} // namespace Interprocess } // namespace Interprocess

Просмотреть файл

@ -2,18 +2,13 @@
#include <boost/interprocess/containers/vector.hpp> #include <boost/interprocess/containers/vector.hpp>
namespace L4 namespace L4 {
{ namespace Interprocess {
namespace Interprocess namespace Container {
{
namespace Container
{
template <typename T, typename Allocator> template <typename T, typename Allocator>
using Vector = boost::interprocess::vector<T, Allocator>; using Vector = boost::interprocess::vector<T, Allocator>;
} // namespace Container } // namespace Container
} // namespace Interprocess } // namespace Interprocess
} // namespace L4 } // namespace L4

Просмотреть файл

@ -4,17 +4,13 @@
#include <type_traits> #include <type_traits>
#include "Utils/Windows.h" #include "Utils/Windows.h"
namespace L4 namespace L4 {
{ namespace Interprocess {
namespace Interprocess namespace Utils {
{
namespace Utils
{
// Handle is a RAII class that manages the life time of the given HANDLE. // Handle is a RAII class that manages the life time of the given HANDLE.
class Handle class Handle {
{ public:
public:
// If verifyHandle is true, it checks whether a given handle is valid. // If verifyHandle is true, it checks whether a given handle is valid.
explicit Handle(HANDLE handle, bool verifyHandle = false); explicit Handle(HANDLE handle, bool verifyHandle = false);
@ -26,10 +22,11 @@ public:
Handle& operator=(const Handle&) = delete; Handle& operator=(const Handle&) = delete;
Handle& operator=(Handle&&) = delete; Handle& operator=(Handle&&) = delete;
private: private:
HANDLE Verify(HANDLE handle, bool verifyHandle) const; HANDLE Verify(HANDLE handle, bool verifyHandle) const;
std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&::CloseHandle)> m_handle; std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&::CloseHandle)>
m_handle;
}; };
} // namespace Utils } // namespace Utils

Просмотреть файл

@ -4,50 +4,40 @@
#include "EpochManager.h" #include "EpochManager.h"
#include "HashTableManager.h" #include "HashTableManager.h"
namespace L4 namespace L4 {
{ namespace LocalMemory {
namespace LocalMemory
{
class Context : private EpochRefPolicy<EpochManager::TheEpochRefManager> class Context : private EpochRefPolicy<EpochManager::TheEpochRefManager> {
{ public:
public: Context(HashTableManager& hashTableManager,
Context(
HashTableManager& hashTableManager,
EpochManager::TheEpochRefManager& epochRefManager) EpochManager::TheEpochRefManager& epochRefManager)
: EpochRefPolicy<EpochManager::TheEpochRefManager>(epochRefManager) : EpochRefPolicy<EpochManager::TheEpochRefManager>(epochRefManager),
, m_hashTableManager{ hashTableManager } m_hashTableManager{hashTableManager} {}
{}
Context(Context&& context) Context(Context&& context)
: EpochRefPolicy<EpochManager::TheEpochRefManager>(std::move(context)) : EpochRefPolicy<EpochManager::TheEpochRefManager>(std::move(context)),
, m_hashTableManager{ context.m_hashTableManager } m_hashTableManager{context.m_hashTableManager} {}
{}
const IReadOnlyHashTable& operator[](const char* name) const const IReadOnlyHashTable& operator[](const char* name) const {
{
return m_hashTableManager.GetHashTable(name); return m_hashTableManager.GetHashTable(name);
} }
IWritableHashTable& operator[](const char* name) IWritableHashTable& operator[](const char* name) {
{
return m_hashTableManager.GetHashTable(name); return m_hashTableManager.GetHashTable(name);
} }
const IReadOnlyHashTable& operator[](std::size_t index) const const IReadOnlyHashTable& operator[](std::size_t index) const {
{
return m_hashTableManager.GetHashTable(index); return m_hashTableManager.GetHashTable(index);
} }
IWritableHashTable& operator[](std::size_t index) IWritableHashTable& operator[](std::size_t index) {
{
return m_hashTableManager.GetHashTable(index); return m_hashTableManager.GetHashTable(index);
} }
Context(const Context&) = delete; Context(const Context&) = delete;
Context& operator=(const Context&) = delete; Context& operator=(const Context&) = delete;
private: private:
HashTableManager& m_hashTableManager; HashTableManager& m_hashTableManager;
}; };

Просмотреть файл

@ -10,87 +10,80 @@
#include "Utils/Lock.h" #include "Utils/Lock.h"
#include "Utils/RunningThread.h" #include "Utils/RunningThread.h"
namespace L4 namespace L4 {
{ namespace LocalMemory {
namespace LocalMemory
{
// EpochManager aggregates epoch-related functionalities such as adding/removing // EpochManager aggregates epoch-related functionalities such as adding/removing
// client epoch queues, registering/performing actions, and updating the epoch counters. // client epoch queues, registering/performing actions, and updating the epoch
class EpochManager : public IEpochActionManager // counters.
{ class EpochManager : public IEpochActionManager {
public: public:
using TheEpochQueue = EpochQueue< using TheEpochQueue =
boost::shared_lock_guard<Utils::ReaderWriterLockSlim>, EpochQueue<boost::shared_lock_guard<Utils::ReaderWriterLockSlim>,
std::lock_guard<Utils::ReaderWriterLockSlim>>; std::lock_guard<Utils::ReaderWriterLockSlim>>;
using TheEpochRefManager = EpochRefManager<TheEpochQueue>; using TheEpochRefManager = EpochRefManager<TheEpochQueue>;
EpochManager( EpochManager(const EpochManagerConfig& config, ServerPerfData& perfData)
const EpochManagerConfig& config, : m_perfData{perfData},
ServerPerfData& perfData) m_config{config},
: m_perfData{ perfData } m_currentEpochCounter{0U},
, m_config{ config } m_epochQueue{m_currentEpochCounter, m_config.m_epochQueueSize},
, m_currentEpochCounter{ 0U } m_epochRefManager{m_epochQueue},
, m_epochQueue{ m_epochCounterManager{m_epochQueue},
m_currentEpochCounter, m_epochActionManager{config.m_numActionQueues},
m_config.m_epochQueueSize } m_processingThread{m_config.m_epochProcessingInterval, [this] {
, m_epochRefManager{ m_epochQueue }
, m_epochCounterManager{ m_epochQueue }
, m_epochActionManager{ config.m_numActionQueues }
, m_processingThread{
m_config.m_epochProcessingInterval,
[this]
{
this->Remove(); this->Remove();
this->Add(); this->Add();
}} }} {}
{}
TheEpochRefManager& GetEpochRefManager() TheEpochRefManager& GetEpochRefManager() { return m_epochRefManager; }
{
return m_epochRefManager;
}
void RegisterAction(Action&& action) override void RegisterAction(Action&& action) override {
{ m_epochActionManager.RegisterAction(m_currentEpochCounter,
m_epochActionManager.RegisterAction(m_currentEpochCounter, std::move(action)); std::move(action));
m_perfData.Increment(ServerPerfCounter::PendingActionsCount); m_perfData.Increment(ServerPerfCounter::PendingActionsCount);
} }
EpochManager(const EpochManager&) = delete; EpochManager(const EpochManager&) = delete;
EpochManager& operator=(const EpochManager&) = delete; EpochManager& operator=(const EpochManager&) = delete;
private: private:
using TheEpochCounterManager = EpochCounterManager<TheEpochQueue>; using TheEpochCounterManager = EpochCounterManager<TheEpochQueue>;
using ProcessingThread = Utils::RunningThread<std::function<void()>>; using ProcessingThread = Utils::RunningThread<std::function<void()>>;
// Enqueues a new epoch whose counter value is last counter + 1. // Enqueues a new epoch whose counter value is last counter + 1.
// This is called from the server side. // This is called from the server side.
void Add() void Add() {
{
// Incrementing the global epoch counter before incrementing per-connection // Incrementing the global epoch counter before incrementing per-connection
// epoch counter is safe (not so the other way around). If the server process is // epoch counter is safe (not so the other way around). If the server
// registering an action at the m_currentEpochCounter in RegisterAction(), // process is registering an action at the m_currentEpochCounter in
// it is happening in the "future," and this means that if the client is referencing // RegisterAction(), it is happening in the "future," and this means that if
// the memory to be deleted in the "future," it will be safe. // the client is referencing the memory to be deleted in the "future," it
// will be safe.
++m_currentEpochCounter; ++m_currentEpochCounter;
m_epochCounterManager.AddNewEpoch(); m_epochCounterManager.AddNewEpoch();
} }
// Dequeues any epochs whose ref counter is 0, meaning there is no reference at that time. // Dequeues any epochs whose ref counter is 0, meaning there is no reference
void Remove() // at that time.
{ void Remove() {
const auto oldestEpochCounter = m_epochCounterManager.RemoveUnreferenceEpochCounters(); const auto oldestEpochCounter =
m_epochCounterManager.RemoveUnreferenceEpochCounters();
const auto numActionsPerformed = m_epochActionManager.PerformActions(oldestEpochCounter); const auto numActionsPerformed =
m_epochActionManager.PerformActions(oldestEpochCounter);
m_perfData.Subtract(ServerPerfCounter::PendingActionsCount, numActionsPerformed); m_perfData.Subtract(ServerPerfCounter::PendingActionsCount,
m_perfData.Set(ServerPerfCounter::LastPerformedActionsCount, numActionsPerformed); numActionsPerformed);
m_perfData.Set(ServerPerfCounter::OldestEpochCounterInQueue, oldestEpochCounter); m_perfData.Set(ServerPerfCounter::LastPerformedActionsCount,
m_perfData.Set(ServerPerfCounter::LatestEpochCounterInQueue, m_currentEpochCounter); numActionsPerformed);
m_perfData.Set(ServerPerfCounter::OldestEpochCounterInQueue,
oldestEpochCounter);
m_perfData.Set(ServerPerfCounter::LatestEpochCounterInQueue,
m_currentEpochCounter);
} }
// Reference to the performance data. // Reference to the performance data.
@ -112,7 +105,8 @@ private:
// Handles adding/decrementing ref counts. // Handles adding/decrementing ref counts.
TheEpochRefManager m_epochRefManager; TheEpochRefManager m_epochRefManager;
// Handles adding new epoch and finding the epoch counts that have zero ref counts. // Handles adding new epoch and finding the epoch counts that have zero ref
// counts.
TheEpochCounterManager m_epochCounterManager; TheEpochCounterManager m_epochCounterManager;
// Handles registering/performing actions. // Handles registering/performing actions.

Просмотреть файл

@ -3,75 +3,71 @@
#include <boost/any.hpp> #include <boost/any.hpp>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "LocalMemory/Memory.h"
#include "Epoch/IEpochActionManager.h" #include "Epoch/IEpochActionManager.h"
#include "HashTable/Cache/HashTable.h"
#include "HashTable/Config.h" #include "HashTable/Config.h"
#include "HashTable/ReadWrite/HashTable.h" #include "HashTable/ReadWrite/HashTable.h"
#include "HashTable/ReadWrite/Serializer.h" #include "HashTable/ReadWrite/Serializer.h"
#include "HashTable/Cache/HashTable.h" #include "LocalMemory/Memory.h"
#include "Utils/Containers.h" #include "Utils/Containers.h"
#include "Utils/Exception.h" #include "Utils/Exception.h"
namespace L4 namespace L4 {
{ namespace LocalMemory {
namespace LocalMemory
{
class HashTableManager class HashTableManager {
{ public:
public:
template <typename Allocator> template <typename Allocator>
std::size_t Add( std::size_t Add(const HashTableConfig& config,
const HashTableConfig& config,
IEpochActionManager& epochActionManager, IEpochActionManager& epochActionManager,
Allocator allocator) Allocator allocator) {
{ if (m_hashTableNameToIndex.find(config.m_name) !=
if (m_hashTableNameToIndex.find(config.m_name) != m_hashTableNameToIndex.end()) m_hashTableNameToIndex.end()) {
{
throw RuntimeException("Same hash table name already exists."); throw RuntimeException("Same hash table name already exists.");
} }
const auto& cacheConfig = config.m_cache; const auto& cacheConfig = config.m_cache;
const auto& serializerConfig = config.m_serializer; const auto& serializerConfig = config.m_serializer;
if (cacheConfig && serializerConfig) if (cacheConfig && serializerConfig) {
{
throw RuntimeException( throw RuntimeException(
"Constructing cache hash table via serializer is not supported."); "Constructing cache hash table via serializer is not supported.");
} }
using namespace HashTable; using namespace HashTable;
using InternalHashTable = typename ReadWrite::WritableHashTable<Allocator>::HashTable; using InternalHashTable =
typename ReadWrite::WritableHashTable<Allocator>::HashTable;
using Memory = typename LocalMemory::Memory<Allocator>; using Memory = typename LocalMemory::Memory<Allocator>;
Memory memory{ allocator }; Memory memory{allocator};
std::shared_ptr<InternalHashTable> internalHashTable = (serializerConfig && serializerConfig->m_stream != nullptr) std::shared_ptr<InternalHashTable> internalHashTable =
? ReadWrite::Deserializer<Memory, InternalHashTable, ReadWrite::WritableHashTable>( (serializerConfig && serializerConfig->m_stream != nullptr)
serializerConfig->m_properties.get_value_or(HashTableConfig::Serializer::Properties())). ? ReadWrite::Deserializer<Memory, InternalHashTable,
Deserialize( ReadWrite::WritableHashTable>(
memory, serializerConfig->m_properties.get_value_or(
*(serializerConfig->m_stream)) HashTableConfig::Serializer::Properties()))
.Deserialize(memory, *(serializerConfig->m_stream))
: memory.template MakeUnique<InternalHashTable>( : memory.template MakeUnique<InternalHashTable>(
typename InternalHashTable::Setting{ typename InternalHashTable::Setting{
config.m_setting.m_numBuckets, config.m_setting.m_numBuckets,
(std::max)(config.m_setting.m_numBucketsPerMutex.get_value_or(1U), 1U), (std::max)(
config.m_setting.m_numBucketsPerMutex.get_value_or(
1U),
1U),
config.m_setting.m_fixedKeySize.get_value_or(0U), config.m_setting.m_fixedKeySize.get_value_or(0U),
config.m_setting.m_fixedValueSize.get_value_or(0U) }, config.m_setting.m_fixedValueSize.get_value_or(0U)},
memory.GetAllocator()); memory.GetAllocator());
auto hashTable = auto hashTable =
cacheConfig cacheConfig ? std::make_unique<Cache::WritableHashTable<Allocator>>(
? std::make_unique<Cache::WritableHashTable<Allocator>>( *internalHashTable, epochActionManager,
*internalHashTable,
epochActionManager,
cacheConfig->m_maxCacheSizeInBytes, cacheConfig->m_maxCacheSizeInBytes,
cacheConfig->m_recordTimeToLive, cacheConfig->m_recordTimeToLive,
cacheConfig->m_forceTimeBasedEviction) cacheConfig->m_forceTimeBasedEviction)
: std::make_unique<ReadWrite::WritableHashTable<Allocator>>( : std::make_unique<ReadWrite::WritableHashTable<Allocator>>(
*internalHashTable, *internalHashTable, epochActionManager);
epochActionManager);
m_internalHashTables.emplace_back(std::move(internalHashTable)); m_internalHashTables.emplace_back(std::move(internalHashTable));
m_hashTables.emplace_back(std::move(hashTable)); m_hashTables.emplace_back(std::move(hashTable));
@ -83,19 +79,17 @@ public:
return newIndex; return newIndex;
} }
IWritableHashTable& GetHashTable(const char* name) IWritableHashTable& GetHashTable(const char* name) {
{
assert(m_hashTableNameToIndex.find(name) != m_hashTableNameToIndex.cend()); assert(m_hashTableNameToIndex.find(name) != m_hashTableNameToIndex.cend());
return GetHashTable(m_hashTableNameToIndex.find(name)->second); return GetHashTable(m_hashTableNameToIndex.find(name)->second);
} }
IWritableHashTable& GetHashTable(std::size_t index) IWritableHashTable& GetHashTable(std::size_t index) {
{
assert(index < m_hashTables.size()); assert(index < m_hashTables.size());
return *m_hashTables[index]; return *m_hashTables[index];
} }
private: private:
Utils::StdStringKeyMap<std::size_t> m_hashTableNameToIndex; Utils::StdStringKeyMap<std::size_t> m_hashTableNameToIndex;
std::vector<boost::any> m_internalHashTables; std::vector<boost::any> m_internalHashTables;

Просмотреть файл

@ -5,33 +5,26 @@
#include "HashTable/Config.h" #include "HashTable/Config.h"
#include "Log/PerfCounter.h" #include "Log/PerfCounter.h"
namespace L4 namespace L4 {
{ namespace LocalMemory {
namespace LocalMemory
{
class HashTableService class HashTableService {
{ public:
public:
explicit HashTableService( explicit HashTableService(
const EpochManagerConfig& epochManagerConfig = EpochManagerConfig()) const EpochManagerConfig& epochManagerConfig = EpochManagerConfig())
: m_epochManager{ epochManagerConfig, m_serverPerfData } : m_epochManager{epochManagerConfig, m_serverPerfData} {}
{}
template <typename Allocator = std::allocator<void>> template <typename Allocator = std::allocator<void>>
std::size_t AddHashTable( std::size_t AddHashTable(const HashTableConfig& config,
const HashTableConfig& config, Allocator allocator = Allocator()) {
Allocator allocator = Allocator())
{
return m_hashTableManager.Add(config, m_epochManager, allocator); return m_hashTableManager.Add(config, m_epochManager, allocator);
} }
Context GetContext() Context GetContext() {
{
return Context(m_hashTableManager, m_epochManager.GetEpochRefManager()); return Context(m_hashTableManager, m_epochManager.GetEpochRefManager());
} }
private: private:
ServerPerfData m_serverPerfData; ServerPerfData m_serverPerfData;
HashTableManager m_hashTableManager; HashTableManager m_hashTableManager;

Просмотреть файл

@ -1,15 +1,12 @@
#pragma once #pragma once
namespace L4 namespace L4 {
{ namespace LocalMemory {
namespace LocalMemory
{
// Simple local memory model that stores the given allocator object. // Simple local memory model that stores the given allocator object.
template <typename Alloc> template <typename Alloc>
class Memory class Memory {
{ public:
public:
using Allocator = Alloc; using Allocator = Alloc;
template <typename T> template <typename T>
@ -18,31 +15,24 @@ public:
template <typename T> template <typename T>
using Deleter = typename std::default_delete<T>; using Deleter = typename std::default_delete<T>;
explicit Memory(Allocator allocator = Allocator()) explicit Memory(Allocator allocator = Allocator()) : m_allocator{allocator} {}
: m_allocator{ allocator }
{}
template <typename T, typename... Args> template <typename T, typename... Args>
auto MakeUnique(Args&&... args) auto MakeUnique(Args&&... args) {
{
return std::make_unique<T>(std::forward<Args>(args)...); return std::make_unique<T>(std::forward<Args>(args)...);
} }
Allocator GetAllocator() Allocator GetAllocator() { return Allocator(m_allocator); }
{
return Allocator(m_allocator);
}
template <typename T> template <typename T>
auto GetDeleter() auto GetDeleter() {
{
return Deleter<T>(); return Deleter<T>();
} }
Memory(const Memory&) = delete; Memory(const Memory&) = delete;
Memory& operator=(const Memory&) = delete; Memory& operator=(const Memory&) = delete;
private: private:
Allocator m_allocator; Allocator m_allocator;
}; };

Просмотреть файл

@ -3,14 +3,10 @@
#include <map> #include <map>
#include "PerfCounter.h" #include "PerfCounter.h"
namespace L4 {
namespace L4
{
// IPerfLogger interface. // IPerfLogger interface.
struct IPerfLogger struct IPerfLogger {
{
struct IData; struct IData;
virtual ~IPerfLogger() = default; virtual ~IPerfLogger() = default;
@ -18,14 +14,12 @@ struct IPerfLogger
virtual void Log(const IData& data) = 0; virtual void Log(const IData& data) = 0;
}; };
// IPerfLogger::IData interface that provides access to ServerPerfData and the aggregated HashTablePerfData. // IPerfLogger::IData interface that provides access to ServerPerfData and the
// Note that the user of IPerfLogger only needs to implement IPerfLogger since IPerfLogger::IData is // aggregated HashTablePerfData. Note that the user of IPerfLogger only needs to
// implemented internally. // implement IPerfLogger since IPerfLogger::IData is implemented internally.
struct IPerfLogger::IData struct IPerfLogger::IData {
{ using HashTablesPerfData =
using HashTablesPerfData = std::map< std::map<std::string, std::reference_wrapper<const HashTablePerfData>>;
std::string,
std::reference_wrapper<const HashTablePerfData>>;
virtual ~IData() = default; virtual ~IData() = default;
@ -34,5 +28,4 @@ struct IPerfLogger::IData
virtual const HashTablesPerfData& GetHashTablesPerfData() const = 0; virtual const HashTablesPerfData& GetHashTablesPerfData() const = 0;
}; };
} // namespace L4 } // namespace L4

Просмотреть файл

@ -1,16 +1,14 @@
#pragma once #pragma once
#include <cstdint>
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <limits>
#include <atomic> #include <atomic>
#include <cstdint>
#include <limits>
namespace L4 namespace L4 {
{
enum class ServerPerfCounter : std::uint16_t enum class ServerPerfCounter : std::uint16_t {
{
// Connection Manager // Connection Manager
ClientConnectionsCount = 0U, ClientConnectionsCount = 0U,
@ -23,22 +21,17 @@ enum class ServerPerfCounter : std::uint16_t
Count Count
}; };
const std::array< const std::array<const char*,
const char*, static_cast<std::uint16_t>(ServerPerfCounter::Count)>
static_cast<std::uint16_t>(ServerPerfCounter::Count)> c_serverPerfCounterNames = c_serverPerfCounterNames = {
{
// Connection Manager // Connection Manager
"ClientConnectionsCount", "ClientConnectionsCount",
// EpochManager // EpochManager
"OldestEpochCounterInQueue", "OldestEpochCounterInQueue", "LatestEpochCounterInQueue",
"LatestEpochCounterInQueue", "PendingActionsCount", "LastPerformedActionsCount"};
"PendingActionsCount",
"LastPerformedActionsCount"
};
enum class HashTablePerfCounter : std::uint16_t enum class HashTablePerfCounter : std::uint16_t {
{
RecordsCount = 0U, RecordsCount = 0U,
BucketsCount, BucketsCount,
TotalKeySize, TotalKeySize,
@ -65,11 +58,9 @@ enum class HashTablePerfCounter : std::uint16_t
Count Count
}; };
const std::array< const std::array<const char*,
const char*, static_cast<std::uint16_t>(HashTablePerfCounter::Count)>
static_cast<std::uint16_t>(HashTablePerfCounter::Count)> c_hashTablePerfCounterNames = c_hashTablePerfCounterNames = {"RecordsCount",
{
"RecordsCount",
"BucketsCount", "BucketsCount",
"TotalKeySize", "TotalKeySize",
"TotalValueSize", "TotalValueSize",
@ -84,114 +75,92 @@ const std::array<
"RecordsCountSavedFromSerializer", "RecordsCountSavedFromSerializer",
"CacheHitCount", "CacheHitCount",
"CacheMissCount", "CacheMissCount",
"EvictedRecordsCount" "EvictedRecordsCount"};
};
template <typename TCounterEnum> template <typename TCounterEnum>
class PerfCounters class PerfCounters {
{ public:
public:
typedef std::int64_t TValue; typedef std::int64_t TValue;
typedef std::atomic<TValue> TCounter; typedef std::atomic<TValue> TCounter;
PerfCounters() PerfCounters() {
{ std::for_each(std::begin(m_counters), std::end(m_counters),
std::for_each( [](TCounter& counter) { counter = 0; });
std::begin(m_counters),
std::end(m_counters),
[] (TCounter& counter)
{
counter = 0;
});
} }
// Note that since the ordering doesn't matter when the counter is updated, memory_order_relaxed // Note that since the ordering doesn't matter when the counter is updated,
// is used for all perf counter updates. // memory_order_relaxed is used for all perf counter updates. More from
// More from http://en.cppreference.com/w/cpp/atomic/memory_order: // http://en.cppreference.com/w/cpp/atomic/memory_order: Typical use for
// Typical use for relaxed memory ordering is updating counters, such as the reference counters // relaxed memory ordering is updating counters, such as the reference
// of std::shared_ptr, since this only requires atomicity, but not ordering or synchronization. // counters of std::shared_ptr, since this only requires atomicity, but not
TValue Get(TCounterEnum counterEnum) const // ordering or synchronization.
{ TValue Get(TCounterEnum counterEnum) const {
return m_counters[static_cast<std::uint16_t>(counterEnum)].load(std::memory_order_relaxed); return m_counters[static_cast<std::uint16_t>(counterEnum)].load(
std::memory_order_relaxed);
} }
void Set(TCounterEnum counterEnum, TValue value) void Set(TCounterEnum counterEnum, TValue value) {
{ m_counters[static_cast<std::uint16_t>(counterEnum)].store(
m_counters[static_cast<std::uint16_t>(counterEnum)].store(value, std::memory_order_relaxed); value, std::memory_order_relaxed);
} }
void Increment(TCounterEnum counterEnum) void Increment(TCounterEnum counterEnum) {
{ m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_add(
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_add(1, std::memory_order_relaxed); 1, std::memory_order_relaxed);
} }
void Decrement(TCounterEnum counterEnum) void Decrement(TCounterEnum counterEnum) {
{ m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_sub(
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_sub(1, std::memory_order_relaxed); 1, std::memory_order_relaxed);
} }
void Add(TCounterEnum counterEnum, TValue value) void Add(TCounterEnum counterEnum, TValue value) {
{ if (value != 0) {
if (value != 0) m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_add(
{ value, std::memory_order_relaxed);
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_add(value, std::memory_order_relaxed);
} }
} }
void Subtract(TCounterEnum counterEnum, TValue value) void Subtract(TCounterEnum counterEnum, TValue value) {
{ if (value != 0) {
if (value != 0) m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_sub(
{ value, std::memory_order_relaxed);
m_counters[static_cast<std::uint16_t>(counterEnum)].fetch_sub(value, std::memory_order_relaxed);
} }
} }
void Max(TCounterEnum counterEnum, TValue value) void Max(TCounterEnum counterEnum, TValue value) {
{
auto& counter = m_counters[static_cast<std::uint16_t>(counterEnum)]; auto& counter = m_counters[static_cast<std::uint16_t>(counterEnum)];
TValue startValue = counter.load(std::memory_order_acquire); TValue startValue = counter.load(std::memory_order_acquire);
do do {
{
// "load()" from counter is needed only once since the value of Max is // "load()" from counter is needed only once since the value of Max is
// monotonically increasing. If startValue is changed by other threads, // monotonically increasing. If startValue is changed by other threads,
// compare_exchange_strong will return false and startValue will be // compare_exchange_strong will return false and startValue will be
// written to the latest value, thus returning to this code path. // written to the latest value, thus returning to this code path.
if (startValue > value) if (startValue > value) {
{
return; return;
} }
} } while (!counter.compare_exchange_strong(startValue, value,
while (!counter.compare_exchange_strong(
startValue,
value,
std::memory_order_release, std::memory_order_release,
std::memory_order_acquire)); std::memory_order_acquire));
} }
void Min(TCounterEnum counterEnum, TValue value) void Min(TCounterEnum counterEnum, TValue value) {
{
auto& counter = m_counters[static_cast<std::uint16_t>(counterEnum)]; auto& counter = m_counters[static_cast<std::uint16_t>(counterEnum)];
TValue startValue = counter.load(std::memory_order_acquire); TValue startValue = counter.load(std::memory_order_acquire);
do do {
{
// Check the comment in Max() and Min() is monotonically decreasing. // Check the comment in Max() and Min() is monotonically decreasing.
if (startValue < value) if (startValue < value) {
{
return; return;
} }
} } while (!counter.compare_exchange_strong(startValue, value,
while (!counter.compare_exchange_strong(
startValue,
value,
std::memory_order_release, std::memory_order_release,
std::memory_order_acquire)); std::memory_order_acquire));
} }
private: private:
#if defined(_MSC_VER) #if defined(_MSC_VER)
__declspec(align(8)) TCounter m_counters[TCounterEnum::Count]; __declspec(align(8)) TCounter m_counters[TCounterEnum::Count];
#else #else
@ -204,12 +173,11 @@ private:
typedef PerfCounters<ServerPerfCounter> ServerPerfData; typedef PerfCounters<ServerPerfCounter> ServerPerfData;
struct HashTablePerfData : public PerfCounters<HashTablePerfCounter> struct HashTablePerfData : public PerfCounters<HashTablePerfCounter> {
{ HashTablePerfData() {
HashTablePerfData()
{
// Initialize any min counters to the max value. // Initialize any min counters to the max value.
const auto maxValue = (std::numeric_limits<HashTablePerfData::TValue>::max)(); const auto maxValue =
(std::numeric_limits<HashTablePerfData::TValue>::max)();
Set(HashTablePerfCounter::MinValueSize, maxValue); Set(HashTablePerfCounter::MinValueSize, maxValue);
Set(HashTablePerfCounter::MinKeySize, maxValue); Set(HashTablePerfCounter::MinKeySize, maxValue);

Просмотреть файл

@ -2,20 +2,16 @@
#include "IPerfLogger.h" #include "IPerfLogger.h"
namespace L4 namespace L4 {
{
struct PerfLoggerManagerConfig; struct PerfLoggerManagerConfig;
// PerfData class, which holds the ServerPerfData and HashTablePerfData for each
// hash table. Note that PerfData owns the ServerPerfData but has only the const
// references to HashTablePerfData, which is owned by the HashTable.
// PerfData class, which holds the ServerPerfData and HashTablePerfData for each hash table. class PerfData : public IPerfLogger::IData {
// Note that PerfData owns the ServerPerfData but has only the const references to HashTablePerfData, public:
// which is owned by the HashTable.
class PerfData : public IPerfLogger::IData
{
public:
PerfData() = default; PerfData() = default;
ServerPerfData& GetServerPerfData(); ServerPerfData& GetServerPerfData();
@ -24,33 +20,30 @@ public:
const HashTablesPerfData& GetHashTablesPerfData() const override; const HashTablesPerfData& GetHashTablesPerfData() const override;
void AddHashTablePerfData(const char* hashTableName, const HashTablePerfData& perfData); void AddHashTablePerfData(const char* hashTableName,
const HashTablePerfData& perfData);
PerfData(const PerfData&) = delete; PerfData(const PerfData&) = delete;
PerfData& operator=(const PerfData&) = delete; PerfData& operator=(const PerfData&) = delete;
private: private:
ServerPerfData m_serverPerfData; ServerPerfData m_serverPerfData;
HashTablesPerfData m_hashTablesPerfData; HashTablesPerfData m_hashTablesPerfData;
}; };
// PerfData inline implementations. // PerfData inline implementations.
inline ServerPerfData& PerfData::GetServerPerfData() inline ServerPerfData& PerfData::GetServerPerfData() {
{
return m_serverPerfData; return m_serverPerfData;
} }
inline const ServerPerfData& PerfData::GetServerPerfData() const inline const ServerPerfData& PerfData::GetServerPerfData() const {
{
return m_serverPerfData; return m_serverPerfData;
} }
inline const PerfData::HashTablesPerfData& PerfData::GetHashTablesPerfData() const inline const PerfData::HashTablesPerfData& PerfData::GetHashTablesPerfData()
{ const {
return m_hashTablesPerfData; return m_hashTablesPerfData;
} }
} // namespace L4 } // namespace L4

Просмотреть файл

@ -3,62 +3,48 @@
#include <cstdint> #include <cstdint>
#include <iosfwd> #include <iosfwd>
namespace L4 namespace L4 {
{
// SerializerHelper provides help functions to write to IStreamWriter. // SerializerHelper provides help functions to write to IStreamWriter.
class SerializerHelper class SerializerHelper {
{ public:
public: SerializerHelper(std::ostream& stream) : m_stream{stream} {}
SerializerHelper(std::ostream& stream)
: m_stream{ stream }
{}
SerializerHelper(const SerializerHelper&) = delete; SerializerHelper(const SerializerHelper&) = delete;
SerializerHelper& operator=(const SerializerHelper&) = delete; SerializerHelper& operator=(const SerializerHelper&) = delete;
template <typename T> template <typename T>
void Serialize(const T& obj) void Serialize(const T& obj) {
{
m_stream.write(reinterpret_cast<const char*>(&obj), sizeof(obj)); m_stream.write(reinterpret_cast<const char*>(&obj), sizeof(obj));
} }
void Serialize(const void* data, std::uint32_t dataSize) void Serialize(const void* data, std::uint32_t dataSize) {
{
m_stream.write(static_cast<const char*>(data), dataSize); m_stream.write(static_cast<const char*>(data), dataSize);
} }
private: private:
std::ostream& m_stream; std::ostream& m_stream;
}; };
// DeserializerHelper provides help functions to read from IStreamReader. // DeserializerHelper provides help functions to read from IStreamReader.
class DeserializerHelper class DeserializerHelper {
{ public:
public: DeserializerHelper(std::istream& stream) : m_stream{stream} {}
DeserializerHelper(std::istream& stream)
: m_stream{ stream }
{}
DeserializerHelper(const DeserializerHelper&) = delete; DeserializerHelper(const DeserializerHelper&) = delete;
DeserializerHelper& operator=(const DeserializerHelper&) = delete; DeserializerHelper& operator=(const DeserializerHelper&) = delete;
template <typename T> template <typename T>
void Deserialize(T& obj) void Deserialize(T& obj) {
{
m_stream.read(reinterpret_cast<char*>(&obj), sizeof(obj)); m_stream.read(reinterpret_cast<char*>(&obj), sizeof(obj));
} }
void Deserialize(void* data, std::uint32_t dataSize) void Deserialize(void* data, std::uint32_t dataSize) {
{
m_stream.read(static_cast<char*>(data), dataSize); m_stream.read(static_cast<char*>(data), dataSize);
} }
private: private:
std::istream& m_stream; std::istream& m_stream;
}; };
} // namespace L4 } // namespace L4

Просмотреть файл

@ -1,53 +1,50 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <cstdint>
#include <boost/version.hpp>
#include <boost/interprocess/offset_ptr.hpp> #include <boost/interprocess/offset_ptr.hpp>
#include <boost/version.hpp>
#include <cstdint>
namespace L4 namespace L4 {
{ namespace Utils {
namespace Utils
{
// AtomicOffsetPtr provides a way to atomically update the offset pointer. // AtomicOffsetPtr provides a way to atomically update the offset pointer.
// The current boost::interprocess::offset_ptr cannot be used with std::atomic<> because // The current boost::interprocess::offset_ptr cannot be used with std::atomic<>
// the class is not trivially copyable. AtomicOffsetPtr borrows the same concept to calculate // because the class is not trivially copyable. AtomicOffsetPtr borrows the same
// the pointer address based on the offset (boost::interprocess::ipcdetail::offset_ptr_to* functions // concept to calculate the pointer address based on the offset
// are reused). // (boost::interprocess::ipcdetail::offset_ptr_to* functions are reused). Note
// Note that ->, *, copy/assignment operators are not implemented intentionally so that // that ->, *, copy/assignment operators are not implemented intentionally so
// the user (inside this library) is aware of what he is intended to do without accidentally // that the user (inside this library) is aware of what he is intended to do
// incurring any performance hits. // without accidentally incurring any performance hits.
template <typename T> template <typename T>
class AtomicOffsetPtr class AtomicOffsetPtr {
{ public:
public: AtomicOffsetPtr() : m_offset(1) {}
AtomicOffsetPtr()
: m_offset(1)
{}
AtomicOffsetPtr(const AtomicOffsetPtr&) = delete; AtomicOffsetPtr(const AtomicOffsetPtr&) = delete;
AtomicOffsetPtr& operator=(const AtomicOffsetPtr&) = delete; AtomicOffsetPtr& operator=(const AtomicOffsetPtr&) = delete;
T* Load(std::memory_order memoryOrder = std::memory_order_seq_cst) const T* Load(std::memory_order memoryOrder = std::memory_order_seq_cst) const {
{
return static_cast<T*>( return static_cast<T*>(
boost::interprocess::ipcdetail::offset_ptr_to_raw_pointer( boost::interprocess::ipcdetail::offset_ptr_to_raw_pointer(
this, this, m_offset.load(memoryOrder)));
m_offset.load(memoryOrder)));
} }
void Store(T* ptr, std::memory_order memoryOrder = std::memory_order_seq_cst) void Store(T* ptr,
{ std::memory_order memoryOrder = std::memory_order_seq_cst) {
#if defined(_MSC_VER) #if defined(_MSC_VER)
m_offset.store(boost::interprocess::ipcdetail::offset_ptr_to_offset(ptr, this), memoryOrder); m_offset.store(
boost::interprocess::ipcdetail::offset_ptr_to_offset(ptr, this),
memoryOrder);
#else #else
m_offset.store(boost::interprocess::ipcdetail::offset_ptr_to_offset<std::uintptr_t>(ptr, this), memoryOrder); m_offset.store(
boost::interprocess::ipcdetail::offset_ptr_to_offset<std::uintptr_t>(
ptr, this),
memoryOrder);
#endif #endif
} }
private: private:
#if defined(_MSC_VER) #if defined(_MSC_VER)
std::atomic_uint64_t m_offset; std::atomic_uint64_t m_offset;
#else #else
@ -55,6 +52,5 @@ private:
#endif #endif
}; };
} // namespace Utils } // namespace Utils
} // namespace L4 } // namespace L4

Просмотреть файл

@ -2,23 +2,16 @@
#include <chrono> #include <chrono>
namespace L4 {
namespace Utils {
namespace L4 class EpochClock {
{ public:
namespace Utils std::chrono::seconds GetCurrentEpochTime() const {
{
class EpochClock
{
public:
std::chrono::seconds GetCurrentEpochTime() const
{
return std::chrono::duration_cast<std::chrono::seconds>( return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()); std::chrono::high_resolution_clock::now().time_since_epoch());
} }
}; };
} // namespace Utils } // namespace Utils
} // namespace L4 } // namespace L4

Просмотреть файл

@ -1,47 +1,40 @@
#pragma once #pragma once
#include <boost/functional/hash.hpp>
#include <cctype> #include <cctype>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <boost/functional/hash.hpp>
#if defined(__GNUC__) #if defined(__GNUC__)
#define _stricmp strcasecmp #define _stricmp strcasecmp
#endif #endif
namespace L4 namespace L4 {
{ namespace Utils {
namespace Utils
{
// CaseInsensitiveStdStringComparer is a STL-compatible case-insensitive ANSI
// CaseInsensitiveStdStringComparer is a STL-compatible case-insensitive ANSI std::string comparer. // std::string comparer.
struct CaseInsensitiveStdStringComparer struct CaseInsensitiveStdStringComparer {
{ bool operator()(const std::string& str1, const std::string& str2) const {
bool operator()(const std::string& str1, const std::string& str2) const
{
return _stricmp(str1.c_str(), str2.c_str()) == 0; return _stricmp(str1.c_str(), str2.c_str()) == 0;
} }
}; };
// CaseInsensitiveStringComparer is a STL-compatible case-insensitive ANSI string comparer. // CaseInsensitiveStringComparer is a STL-compatible case-insensitive ANSI
struct CaseInsensitiveStringComparer // string comparer.
{ struct CaseInsensitiveStringComparer {
bool operator()(const char* const str1, const char* const str2) const bool operator()(const char* const str1, const char* const str2) const {
{
return _stricmp(str1, str2) == 0; return _stricmp(str1, str2) == 0;
} }
}; };
// CaseInsensitiveStringHasher is a STL-compatible case-insensitive ANSI std::string hasher. // CaseInsensitiveStringHasher is a STL-compatible case-insensitive ANSI
struct CaseInsensitiveStdStringHasher // std::string hasher.
{ struct CaseInsensitiveStdStringHasher {
std::size_t operator()(const std::string& str) const std::size_t operator()(const std::string& str) const {
{
std::size_t seed = 0; std::size_t seed = 0;
for (auto c : str) for (auto c : str) {
{
boost::hash_combine(seed, std::toupper(c)); boost::hash_combine(seed, std::toupper(c));
} }
@ -49,17 +42,15 @@ struct CaseInsensitiveStdStringHasher
} }
}; };
// CaseInsensitiveStringHasher is a STL-compatible case-insensitive ANSI string hasher. // CaseInsensitiveStringHasher is a STL-compatible case-insensitive ANSI string
struct CaseInsensitiveStringHasher // hasher.
{ struct CaseInsensitiveStringHasher {
std::size_t operator()(const char* str) const std::size_t operator()(const char* str) const {
{
assert(str != nullptr); assert(str != nullptr);
std::size_t seed = 0; std::size_t seed = 0;
while (*str) while (*str) {
{
boost::hash_combine(seed, std::toupper(*str++)); boost::hash_combine(seed, std::toupper(*str++));
} }
@ -67,6 +58,5 @@ struct CaseInsensitiveStringHasher
} }
}; };
} // namespace Utils } // namespace Utils
} // namespace L4 } // namespace L4

Просмотреть файл

@ -1,23 +1,20 @@
#pragma once #pragma once
#include <boost/functional/hash.hpp>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <boost/functional/hash.hpp>
#include "Utils/ComparerHasher.h" #include "Utils/ComparerHasher.h"
namespace L4 {
namespace Utils {
namespace L4 // StdStringKeyMap is an unordered_map where the key is std::string. It is
{ // slower than StringKeyMap above, but it owns the memory of the string, so it's
namespace Utils // easier to use.
{
// StdStringKeyMap is an unordered_map where the key is std::string. It is slower than
// StringKeyMap above, but it owns the memory of the string, so it's easier to use.
template <typename TValue> template <typename TValue>
using StdStringKeyMap = std::unordered_map< using StdStringKeyMap =
std::string, std::unordered_map<std::string,
TValue, TValue,
Utils::CaseInsensitiveStdStringHasher, Utils::CaseInsensitiveStdStringHasher,
Utils::CaseInsensitiveStdStringComparer>; Utils::CaseInsensitiveStdStringComparer>;
@ -26,20 +23,15 @@ using StdStringKeyMap = std::unordered_map<
// The memory of the key is not owned by StringKeyMap, // The memory of the key is not owned by StringKeyMap,
// but it is faster (than StdStringKeyMap below) for look up. // but it is faster (than StdStringKeyMap below) for look up.
template <typename TValue> template <typename TValue>
using StringKeyMap = std::unordered_map< using StringKeyMap = std::unordered_map<const char*,
const char*,
TValue, TValue,
Utils::CaseInsensitiveStringHasher, Utils::CaseInsensitiveStringHasher,
Utils::CaseInsensitiveStringComparer>; Utils::CaseInsensitiveStringComparer>;
// IntegerKeyMap using boost::hash and std::equal_to comparer and hasher. // IntegerKeyMap using boost::hash and std::equal_to comparer and hasher.
template <typename TKey, typename TValue> template <typename TKey, typename TValue>
using IntegerKeyMap = std::unordered_map< using IntegerKeyMap =
TKey, std::unordered_map<TKey, TValue, boost::hash<TKey>, std::equal_to<TKey>>;
TValue,
boost::hash<TKey>,
std::equal_to<TKey>>;
} // namespace Utils } // namespace Utils
} // namespace L4 } // namespace L4

Просмотреть файл

@ -1,22 +1,18 @@
#pragma once #pragma once
#include <string>
#include <stdexcept> #include <stdexcept>
#include <string>
namespace L4 namespace L4 {
{
// RuntimeException class used across L4 library. // RuntimeException class used across L4 library.
class RuntimeException : public std::runtime_error class RuntimeException : public std::runtime_error {
{ public:
public:
explicit RuntimeException(const std::string& message) explicit RuntimeException(const std::string& message)
: std::runtime_error(message.c_str()) : std::runtime_error(message.c_str()) {}
{}
explicit RuntimeException(const char* message) explicit RuntimeException(const char* message)
: std::runtime_error(message) : std::runtime_error(message) {}
{}
}; };
} // namespace L4 } // namespace L4

Просмотреть файл

@ -12,84 +12,52 @@
#endif #endif
#endif #endif
namespace L4 {
namespace L4 namespace Utils {
{
namespace Utils
{
#if defined(_MSC_VER) #if defined(_MSC_VER)
// Represents a RAII wrapper for Win32 CRITICAL_SECTION. // Represents a RAII wrapper for Win32 CRITICAL_SECTION.
class CriticalSection : protected ::CRITICAL_SECTION class CriticalSection : protected ::CRITICAL_SECTION {
{ public:
public:
// Constructs and initializes the critical section. // Constructs and initializes the critical section.
CriticalSection() CriticalSection() { ::InitializeCriticalSection(this); }
{
::InitializeCriticalSection(this);
}
CriticalSection(const CriticalSection& other) = delete; CriticalSection(const CriticalSection& other) = delete;
CriticalSection& operator=(const CriticalSection& other) = delete; CriticalSection& operator=(const CriticalSection& other) = delete;
// Destructs the critical section. // Destructs the critical section.
~CriticalSection() ~CriticalSection() { ::DeleteCriticalSection(this); }
{
::DeleteCriticalSection(this);
}
// Waits for ownership of the critical section. // Waits for ownership of the critical section.
void lock() void lock() { ::EnterCriticalSection(this); }
{
::EnterCriticalSection(this);
}
// Releases ownership of the critical section. // Releases ownership of the critical section.
void unlock() void unlock() { ::LeaveCriticalSection(this); }
{
::LeaveCriticalSection(this);
}
}; };
// Represents a RAII wrapper for Win32 SRW lock. // Represents a RAII wrapper for Win32 SRW lock.
class ReaderWriterLockSlim class ReaderWriterLockSlim {
{ public:
public:
// Constructs and initializes an SRW lock. // Constructs and initializes an SRW lock.
ReaderWriterLockSlim() ReaderWriterLockSlim() { ::InitializeSRWLock(&m_lock); }
{
::InitializeSRWLock(&m_lock);
}
ReaderWriterLockSlim(const ReaderWriterLockSlim& other) = delete; ReaderWriterLockSlim(const ReaderWriterLockSlim& other) = delete;
ReaderWriterLockSlim& operator=(const ReaderWriterLockSlim& other) = delete; ReaderWriterLockSlim& operator=(const ReaderWriterLockSlim& other) = delete;
// Acquires an SRW lock in shared mode. // Acquires an SRW lock in shared mode.
void lock_shared() void lock_shared() { ::AcquireSRWLockShared(&m_lock); }
{
::AcquireSRWLockShared(&m_lock);
}
// Acquires an SRW lock in exclusive mode. // Acquires an SRW lock in exclusive mode.
void lock() void lock() { ::AcquireSRWLockExclusive(&m_lock); }
{
::AcquireSRWLockExclusive(&m_lock);
}
// Releases an SRW lock that was opened in shared mode. // Releases an SRW lock that was opened in shared mode.
void unlock_shared() void unlock_shared() { ::ReleaseSRWLockShared(&m_lock); }
{
::ReleaseSRWLockShared(&m_lock);
}
// Releases an SRW lock that was opened in exclusive mode. // Releases an SRW lock that was opened in exclusive mode.
void unlock() void unlock() { ::ReleaseSRWLockExclusive(&m_lock); }
{
::ReleaseSRWLockExclusive(&m_lock);
}
private: private:
// Stores the Win32 SRW lock. // Stores the Win32 SRW lock.
::SRWLOCK m_lock; ::SRWLOCK m_lock;
}; };
@ -97,60 +65,38 @@ private:
#else #else
#if defined(__GNUC__) #if defined(__GNUC__)
class CriticalSection class CriticalSection {
{ public:
public: CriticalSection() : m_mutex{} {}
CriticalSection()
: m_mutex{}
{}
CriticalSection(const CriticalSection& other) = delete; CriticalSection(const CriticalSection& other) = delete;
CriticalSection& operator=(const CriticalSection& other) = delete; CriticalSection& operator=(const CriticalSection& other) = delete;
~CriticalSection() = default; ~CriticalSection() = default;
void lock() void lock() { pthread_mutex_lock(&m_mutex); }
{
pthread_mutex_lock(&m_mutex);
}
void unlock() void unlock() { pthread_mutex_unlock(&m_mutex); }
{
pthread_mutex_unlock(&m_mutex);
}
private: private:
pthread_mutex_t m_mutex; pthread_mutex_t m_mutex;
}; };
class ReaderWriterLockSlim class ReaderWriterLockSlim {
{ public:
public:
ReaderWriterLockSlim() = default; ReaderWriterLockSlim() = default;
ReaderWriterLockSlim(const ReaderWriterLockSlim& other) = delete; ReaderWriterLockSlim(const ReaderWriterLockSlim& other) = delete;
ReaderWriterLockSlim& operator=(const ReaderWriterLockSlim& other) = delete; ReaderWriterLockSlim& operator=(const ReaderWriterLockSlim& other) = delete;
void lock_shared() void lock_shared() { pthread_rwlock_rdlock(&m_lock); }
{
pthread_rwlock_rdlock(&m_lock);
}
void lock() void lock() { pthread_rwlock_wrlock(&m_lock); }
{
pthread_rwlock_wrlock(&m_lock);
}
void unlock_shared() void unlock_shared() { pthread_rwlock_unlock(&m_lock); }
{
pthread_rwlock_unlock(&m_lock);
}
void unlock() void unlock() { unlock_shared(); }
{
unlock_shared();
}
private: private:
pthread_rwlock_t m_lock = PTHREAD_RWLOCK_INITIALIZER; pthread_rwlock_t m_lock = PTHREAD_RWLOCK_INITIALIZER;
}; };

Просмотреть файл

@ -1,40 +1,31 @@
#pragma once #pragma once
#include <cstdint>
#include <cstddef>
#include <complex> #include <complex>
#include <cstddef>
#include <cstdint>
namespace L4 {
namespace L4 namespace Utils {
{ namespace Math {
namespace Utils
{
namespace Math
{
// Rounds up the number to the nearest multiple of base. // Rounds up the number to the nearest multiple of base.
inline std::uint64_t RoundUp(std::uint64_t number, std::uint64_t base) inline std::uint64_t RoundUp(std::uint64_t number, std::uint64_t base) {
{
return base ? (((number + base - 1) / base) * base) : number; return base ? (((number + base - 1) / base) * base) : number;
} }
// Rounds down the number to the nearest multiple of base. // Rounds down the number to the nearest multiple of base.
inline std::uint64_t RoundDown(std::uint64_t number, std::uint64_t base) inline std::uint64_t RoundDown(std::uint64_t number, std::uint64_t base) {
{
return base ? ((number / base) * base) : number; return base ? ((number / base) * base) : number;
} }
// Returns true if the given number is a power of 2. // Returns true if the given number is a power of 2.
inline bool IsPowerOfTwo(std::uint64_t number) inline bool IsPowerOfTwo(std::uint64_t number) {
{
return number && ((number & (number - 1)) == 0); return number && ((number & (number - 1)) == 0);
} }
// Returns the next highest power of two from the given value. // Returns the next highest power of two from the given value.
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2. // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2.
inline std::uint32_t NextHighestPowerOfTwo(std::uint32_t val) inline std::uint32_t NextHighestPowerOfTwo(std::uint32_t val) {
{
--val; --val;
val |= val >> 1; val |= val >> 1;
val |= val >> 2; val |= val >> 2;
@ -44,36 +35,30 @@ inline std::uint32_t NextHighestPowerOfTwo(std::uint32_t val)
return ++val; return ++val;
} }
// Provides utility functions doing pointer related arithmetics. // Provides utility functions doing pointer related arithmetics.
namespace PointerArithmetic namespace PointerArithmetic {
{
// Returns a new pointer after adding an offset. // Returns a new pointer after adding an offset.
template <typename T> template <typename T>
inline T* Add(T* ptr, std::size_t offset) inline T* Add(T* ptr, std::size_t offset) {
{
return reinterpret_cast<T*>(reinterpret_cast<std::uintptr_t>(ptr) + offset); return reinterpret_cast<T*>(reinterpret_cast<std::uintptr_t>(ptr) + offset);
} }
// Returns a new pointer after subtracting an offset. // Returns a new pointer after subtracting an offset.
template <typename T> template <typename T>
inline T* Subtract(T* ptr, std::size_t offset) inline T* Subtract(T* ptr, std::size_t offset) {
{
return reinterpret_cast<T*>(reinterpret_cast<std::uintptr_t>(ptr) - offset); return reinterpret_cast<T*>(reinterpret_cast<std::uintptr_t>(ptr) - offset);
} }
// Returns the absolute value of difference in the number of bytes between two pointers. // Returns the absolute value of difference in the number of bytes between two
inline std::size_t Distance(const void* lhs, const void* rhs) // pointers.
{ inline std::size_t Distance(const void* lhs, const void* rhs) {
return std::abs(reinterpret_cast<std::ptrdiff_t>(lhs) - reinterpret_cast<std::ptrdiff_t>(rhs)); return std::abs(reinterpret_cast<std::ptrdiff_t>(lhs) -
reinterpret_cast<std::ptrdiff_t>(rhs));
} }
} // namespace PointerArithmetic } // namespace PointerArithmetic
} // namespace Math } // namespace Math
} // namespace Utils } // namespace Utils
} // namespace L4 } // namespace L4

Просмотреть файл

@ -4,44 +4,33 @@
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
namespace L4 {
namespace L4 namespace Utils {
{
namespace Utils
{
// Properties class represents a string to string map (case insensitive). // Properties class represents a string to string map (case insensitive).
// It can be used where the configurations should be generic. // It can be used where the configurations should be generic.
class Properties : public StdStringKeyMap<std::string> class Properties : public StdStringKeyMap<std::string> {
{ public:
public:
using Base = Utils::StdStringKeyMap<std::string>; using Base = Utils::StdStringKeyMap<std::string>;
using Value = Base::value_type; using Value = Base::value_type;
Properties() = default; Properties() = default;
// Expose a constructor with initializer_list for convenience. // Expose a constructor with initializer_list for convenience.
Properties(std::initializer_list<Value> values) Properties(std::initializer_list<Value> values) : Base(values) {}
: Base(values)
{
}
// Returns true if the given key exists and the value associated with // Returns true if the given key exists and the value associated with
// the key can be converted to the TValue type. If the conversion fails, the value // the key can be converted to the TValue type. If the conversion fails, the
// of the given val is guaranteed to remain the same. // value of the given val is guaranteed to remain the same.
template <typename TValue> template <typename TValue>
bool TryGet(const std::string& key, TValue& val) const bool TryGet(const std::string& key, TValue& val) const {
{
const auto it = find(key); const auto it = find(key);
if (it == end()) if (it == end()) {
{
return false; return false;
} }
TValue tmp; TValue tmp;
if (!boost::conversion::try_lexical_convert(it->second, tmp)) if (!boost::conversion::try_lexical_convert(it->second, tmp)) {
{
return false; return false;
} }
@ -51,6 +40,5 @@ public:
} }
}; };
} // namespace Utils } // namespace Utils
} // namespace L4 } // namespace L4

Просмотреть файл

@ -1,49 +1,34 @@
#pragma once #pragma once
#include <atomic>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <thread> #include <thread>
#include <atomic>
namespace L4
{
namespace Utils
{
namespace L4 {
namespace Utils {
// NoOp is a function object that doesn't do anything. // NoOp is a function object that doesn't do anything.
struct NoOp struct NoOp {
{
void operator()(...) {} void operator()(...) {}
}; };
// RunningThread wraps around std::thread and repeatedly runs a given function after yielding // RunningThread wraps around std::thread and repeatedly runs a given function
// for the given interval. Note that the destructor waits for the thread to stop. // after yielding for the given interval. Note that the destructor waits for the
// thread to stop.
template <typename CoreFunc, typename PrepFunc = NoOp> template <typename CoreFunc, typename PrepFunc = NoOp>
class RunningThread class RunningThread {
{ public:
public: RunningThread(std::chrono::milliseconds interval,
RunningThread(
std::chrono::milliseconds interval,
CoreFunc coreFunc, CoreFunc coreFunc,
PrepFunc prepFunc = PrepFunc()) PrepFunc prepFunc = PrepFunc())
: m_isRunning(), : m_isRunning(),
m_thread( m_thread(&RunningThread::Start, this, interval, coreFunc, prepFunc) {}
&RunningThread::Start,
this,
interval,
coreFunc,
prepFunc)
{
}
~RunningThread() ~RunningThread() {
{
m_isRunning.store(false); m_isRunning.store(false);
if (m_thread.joinable()) if (m_thread.joinable()) {
{
m_thread.join(); m_thread.join();
} }
} }
@ -51,18 +36,15 @@ public:
RunningThread(const RunningThread&) = delete; RunningThread(const RunningThread&) = delete;
RunningThread& operator=(const RunningThread&) = delete; RunningThread& operator=(const RunningThread&) = delete;
private: private:
void Start( void Start(std::chrono::milliseconds interval,
std::chrono::milliseconds interval,
CoreFunc coreFunc, CoreFunc coreFunc,
PrepFunc prepFunc) PrepFunc prepFunc) {
{
m_isRunning.store(true); m_isRunning.store(true);
prepFunc(); prepFunc();
while (m_isRunning.load()) while (m_isRunning.load()) {
{
coreFunc(); coreFunc();
std::this_thread::sleep_for(interval); std::this_thread::sleep_for(interval);
@ -74,6 +56,5 @@ private:
std::thread m_thread; std::thread m_thread;
}; };
} // namespace Utils } // namespace Utils
} // namespace L4 } // namespace L4

Просмотреть файл

@ -2,7 +2,7 @@
// Allow macro redefinition. // Allow macro redefinition.
#pragma warning(push) #pragma warning(push)
#pragma warning(disable:4005) #pragma warning(disable : 4005)
// Explicitly excluding API groups // Explicitly excluding API groups
//#define NOGDICAPMASKS // - CC_*, LC_*, PC_*, CP_*, TC_*, RC_ //#define NOGDICAPMASKS // - CC_*, LC_*, PC_*, CP_*, TC_*, RC_
@ -44,14 +44,15 @@
#define NODEFERWINDOWPOS // - DeferWindowPos routines #define NODEFERWINDOWPOS // - DeferWindowPos routines
#define NOMCX // - Modem Configuration Extensions #define NOMCX // - Modem Configuration Extensions
// Enabling STRICT redefines certain data types so that the compiler does not permit assignment from one type to another without an explicit cast. // Enabling STRICT redefines certain data types so that the compiler does not
// permit assignment from one type to another without an explicit cast.
#define STRICT #define STRICT
// Define WIN32_LEAN_AND_MEAN to exclude APIs such as Cryptography, DDE, RPC, Shell, and Windows Sockets. // Define WIN32_LEAN_AND_MEAN to exclude APIs such as Cryptography, DDE, RPC,
// Cryptography is needed due to <boost/uuids/random_generator.hpp> // Shell, and Windows Sockets. Cryptography is needed due to
// <boost/uuids/random_generator.hpp>
//#define WIN32_LEAN_AND_MEAN //#define WIN32_LEAN_AND_MEAN
#pragma warning(pop) #pragma warning(pop)
#include <Windows.h> #include <Windows.h>

Просмотреть файл

@ -2,14 +2,10 @@
#include <boost/interprocess/detail/utilities.hpp> #include <boost/interprocess/detail/utilities.hpp>
namespace L4 namespace L4 {
{ namespace Detail {
namespace Detail
{
using boost::interprocess::ipcdetail::to_raw_pointer; using boost::interprocess::ipcdetail::to_raw_pointer;
} // namespace Detail } // namespace Detail
} // namespace L4 } // namespace L4

Просмотреть файл

@ -5,36 +5,33 @@
#include <memory> #include <memory>
#include <thread> #include <thread>
namespace L4 namespace L4 {
{
// EpochActionManager class implementation. // EpochActionManager class implementation.
EpochActionManager::EpochActionManager(std::uint8_t numActionQueues) EpochActionManager::EpochActionManager(std::uint8_t numActionQueues)
: m_epochToActionsList{} : m_epochToActionsList{}, m_counter{} {
, m_counter{}
{
// Calculate numActionQueues as the next highest power of two. // Calculate numActionQueues as the next highest power of two.
std::uint16_t newNumActionQueues = numActionQueues; std::uint16_t newNumActionQueues = numActionQueues;
if (numActionQueues == 0U) if (numActionQueues == 0U) {
{ newNumActionQueues =
newNumActionQueues = static_cast<std::uint16_t>(std::thread::hardware_concurrency()); static_cast<std::uint16_t>(std::thread::hardware_concurrency());
} }
newNumActionQueues = static_cast<std::uint16_t>(Utils::Math::NextHighestPowerOfTwo(newNumActionQueues)); newNumActionQueues = static_cast<std::uint16_t>(
Utils::Math::NextHighestPowerOfTwo(newNumActionQueues));
assert(newNumActionQueues != 0U && Utils::Math::IsPowerOfTwo(newNumActionQueues)); assert(newNumActionQueues != 0U &&
Utils::Math::IsPowerOfTwo(newNumActionQueues));
// Initialize m_epochToActionsList. // Initialize m_epochToActionsList.
m_epochToActionsList.resize(newNumActionQueues); m_epochToActionsList.resize(newNumActionQueues);
for (auto& epochToActions : m_epochToActionsList) for (auto& epochToActions : m_epochToActionsList) {
{
std::get<0>(epochToActions) = std::make_unique<Mutex>(); std::get<0>(epochToActions) = std::make_unique<Mutex>();
} }
} }
void EpochActionManager::RegisterAction(std::uint64_t epochCounter,
void EpochActionManager::RegisterAction(std::uint64_t epochCounter, IEpochActionManager::Action&& action) IEpochActionManager::Action&& action) {
{
std::uint32_t index = ++m_counter & (m_epochToActionsList.size() - 1); std::uint32_t index = ++m_counter & (m_epochToActionsList.size() - 1);
auto& epochToActions = m_epochToActionsList[index]; auto& epochToActions = m_epochToActionsList[index];
@ -42,14 +39,11 @@ void EpochActionManager::RegisterAction(std::uint64_t epochCounter, IEpochAction
std::get<1>(epochToActions)[epochCounter].emplace_back(std::move(action)); std::get<1>(epochToActions)[epochCounter].emplace_back(std::move(action));
} }
std::uint64_t EpochActionManager::PerformActions(std::uint64_t epochCounter) {
std::uint64_t EpochActionManager::PerformActions(std::uint64_t epochCounter)
{
// Actions will be moved here and performed without a lock. // Actions will be moved here and performed without a lock.
Actions actionsToPerform; Actions actionsToPerform;
for (auto& epochToActionsWithLock : m_epochToActionsList) for (auto& epochToActionsWithLock : m_epochToActionsList) {
{
Lock lock(*std::get<0>(epochToActionsWithLock)); Lock lock(*std::get<0>(epochToActionsWithLock));
// lower_bound() so that it is deleted up to but not including epochCounter. // lower_bound() so that it is deleted up to but not including epochCounter.
@ -58,14 +52,13 @@ std::uint64_t EpochActionManager::PerformActions(std::uint64_t epochCounter)
auto it = epochToActions.begin(); auto it = epochToActions.begin();
while (it != endIt) while (it != endIt) {
{ actionsToPerform.insert(actionsToPerform.end(),
actionsToPerform.insert(
actionsToPerform.end(),
std::make_move_iterator(it->second.begin()), std::make_move_iterator(it->second.begin()),
std::make_move_iterator(it->second.end())); std::make_move_iterator(it->second.end()));
// The following post increment is intentional to avoid iterator invalidation issue. // The following post increment is intentional to avoid iterator
// invalidation issue.
epochToActions.erase(it++); epochToActions.erase(it++);
} }
} }
@ -75,11 +68,8 @@ std::uint64_t EpochActionManager::PerformActions(std::uint64_t epochCounter)
return actionsToPerform.size(); return actionsToPerform.size();
} }
void EpochActionManager::ApplyActions(Actions& actions) {
void EpochActionManager::ApplyActions(Actions& actions) for (auto& action : actions) {
{
for (auto& action : actions)
{
action(); action();
} }
} }

Просмотреть файл

@ -1,53 +1,41 @@
#include "Interprocess/Connection/ConnectionMonitor.h" #include "Interprocess/Connection/ConnectionMonitor.h"
#include <atomic>
#include "Interprocess/Connection/EndPointInfoUtils.h" #include "Interprocess/Connection/EndPointInfoUtils.h"
#include "Utils/Exception.h" #include "Utils/Exception.h"
#include "Utils/Windows.h" #include "Utils/Windows.h"
#include <atomic>
namespace L4 namespace L4 {
{ namespace Interprocess {
namespace Interprocess namespace Connection {
{
namespace Connection
{
// ConnectionMonitor class implementation. // ConnectionMonitor class implementation.
ConnectionMonitor::ConnectionMonitor() ConnectionMonitor::ConnectionMonitor()
: m_localEndPoint{ EndPointInfoFactory().Create() } : m_localEndPoint{EndPointInfoFactory().Create()},
, m_localEvent{ m_localEvent{::CreateEvent(
::CreateEvent(
NULL, NULL,
TRUE, // Manual reset in order to notify all end points registered. TRUE, // Manual reset in order to notify all end points registered.
FALSE, FALSE,
StringConverter()(m_localEndPoint).c_str()) } StringConverter()(m_localEndPoint).c_str())} {}
{}
ConnectionMonitor::~ConnectionMonitor() {
ConnectionMonitor::~ConnectionMonitor()
{
// Notify the remote endpoints. // Notify the remote endpoints.
::SetEvent(static_cast<HANDLE>(m_localEvent)); ::SetEvent(static_cast<HANDLE>(m_localEvent));
} }
const EndPointInfo& ConnectionMonitor::GetLocalEndPointInfo() const {
const EndPointInfo& ConnectionMonitor::GetLocalEndPointInfo() const
{
return m_localEndPoint; return m_localEndPoint;
} }
std::size_t ConnectionMonitor::GetRemoteConnectionsCount() const {
std::size_t ConnectionMonitor::GetRemoteConnectionsCount() const
{
UnRegister(); UnRegister();
std::lock_guard<std::mutex> lock(m_mutexOnRemoteMonitors); std::lock_guard<std::mutex> lock(m_mutexOnRemoteMonitors);
return m_remoteMonitors.size(); return m_remoteMonitors.size();
} }
void ConnectionMonitor::Register(const EndPointInfo& remoteEndPoint,
void ConnectionMonitor::Register(const EndPointInfo& remoteEndPoint, Callback callback) Callback callback) {
{
UnRegister(); UnRegister();
// The following is needed to prevent the case where the callback is trying // The following is needed to prevent the case where the callback is trying
@ -62,111 +50,97 @@ void ConnectionMonitor::Register(const EndPointInfo& remoteEndPoint, Callback ca
// Note that the following call may throw since opening handles may fail, but // Note that the following call may throw since opening handles may fail, but
// it is exception safe (std::map::emplace has a strong guarantee on it). // it is exception safe (std::map::emplace has a strong guarantee on it).
if (!m_remoteMonitors.emplace( if (!m_remoteMonitors
remoteEndPoint, .emplace(remoteEndPoint,
std::make_unique<HandleMonitor>( std::make_unique<HandleMonitor>(
remoteEndPoint, remoteEndPoint,
[thisWeakPtr, callback, isCalled](const auto& remoteEndPoint) [thisWeakPtr, callback,
{ isCalled](const auto& remoteEndPoint) {
if (isCalled->exchange(true)) if (isCalled->exchange(true)) {
{
return; return;
} }
callback(remoteEndPoint); callback(remoteEndPoint);
auto connectionMonitor = thisWeakPtr.lock(); auto connectionMonitor = thisWeakPtr.lock();
if (connectionMonitor != nullptr) if (connectionMonitor != nullptr) {
{ // Cannot call UnRegister() because it will
// Cannot call UnRegister() because it will self-destruct. // self-destruct. Instead, call the UnRegister(const
// Instead, call the UnRegister(const EndPointInfo&) and queue up the end point // EndPointInfo&) and queue up the end point that
// that will be removed from m_remoteEvents at a later time. // will be removed from m_remoteEvents at a later
// time.
connectionMonitor->UnRegister(remoteEndPoint); connectionMonitor->UnRegister(remoteEndPoint);
} }
})).second) }))
{ .second) {
throw RuntimeException("Duplicate end point found."); throw RuntimeException("Duplicate end point found.");
} }
} }
void ConnectionMonitor::UnRegister(const EndPointInfo& remoteEndPoint) {
void ConnectionMonitor::UnRegister(const EndPointInfo& remoteEndPoint)
{
std::lock_guard<std::mutex> lock(m_mutexOnUnregisteredEndPoints); std::lock_guard<std::mutex> lock(m_mutexOnUnregisteredEndPoints);
m_unregisteredEndPoints.emplace_back(remoteEndPoint); m_unregisteredEndPoints.emplace_back(remoteEndPoint);
} }
void ConnectionMonitor::UnRegister() const {
void ConnectionMonitor::UnRegister() const
{
std::vector<EndPointInfo> unregisteredEndPoints; std::vector<EndPointInfo> unregisteredEndPoints;
{ {
// It is possible that the erase() in the following block can // It is possible that the erase() in the following block can
// wait for the callback to finish (::WaitForThreadpoolWaitCallbacks). // wait for the callback to finish (::WaitForThreadpoolWaitCallbacks).
// Since the callback calls the UnRegister(const EndPointinfo&), it can deadlock // Since the callback calls the UnRegister(const EndPointinfo&), it can
// if this function holds the lock while calling the erase(). // deadlock if this function holds the lock while calling the erase(). Thus,
// Thus, copy the m_unregisteredEndPoints and release the lock before calling erase() below. // copy the m_unregisteredEndPoints and release the lock before calling
// erase() below.
std::lock_guard<std::mutex> lock(m_mutexOnUnregisteredEndPoints); std::lock_guard<std::mutex> lock(m_mutexOnUnregisteredEndPoints);
unregisteredEndPoints.swap(m_unregisteredEndPoints); unregisteredEndPoints.swap(m_unregisteredEndPoints);
} }
std::lock_guard<std::mutex> lock(m_mutexOnRemoteMonitors); std::lock_guard<std::mutex> lock(m_mutexOnRemoteMonitors);
for (const auto& endPoint : unregisteredEndPoints) for (const auto& endPoint : unregisteredEndPoints) {
{
m_remoteMonitors.erase(endPoint); m_remoteMonitors.erase(endPoint);
} }
} }
// ConnectionMonitor::HandleMonitor::HandleMonitor class implementation. // ConnectionMonitor::HandleMonitor::HandleMonitor class implementation.
ConnectionMonitor::HandleMonitor::HandleMonitor( ConnectionMonitor::HandleMonitor::HandleMonitor(
const EndPointInfo& remoteEndPoint, const EndPointInfo& remoteEndPoint,
Callback callback) Callback callback)
: m_eventWaiter{ : m_eventWaiter{std::make_unique<Waiter>(
std::make_unique<Waiter>( Utils::Handle{::OpenEvent(SYNCHRONIZE,
Utils::Handle{ ::OpenEvent(SYNCHRONIZE, FALSE, StringConverter()(remoteEndPoint).c_str()) }, FALSE,
[callback, endPoint = remoteEndPoint] { callback(endPoint); }) } StringConverter()(remoteEndPoint).c_str())},
, m_processWaiter{ [callback, endPoint = remoteEndPoint] { callback(endPoint); })},
std::make_unique<Waiter>( m_processWaiter{std::make_unique<Waiter>(
Utils::Handle{ ::OpenProcess(SYNCHRONIZE, FALSE, remoteEndPoint.m_pid) }, Utils::Handle{
[callback, endPoint = remoteEndPoint] { callback(endPoint); }) } ::OpenProcess(SYNCHRONIZE, FALSE, remoteEndPoint.m_pid)},
{} [callback, endPoint = remoteEndPoint] { callback(endPoint); })} {}
// ConnectionMonitor::HandleMonitor::Waiter class implementation. // ConnectionMonitor::HandleMonitor::Waiter class implementation.
ConnectionMonitor::HandleMonitor::Waiter::Waiter(Utils::Handle handle, Callback callback) ConnectionMonitor::HandleMonitor::Waiter::Waiter(Utils::Handle handle,
: m_handle{ std::move(handle) } Callback callback)
, m_callback{ callback } : m_handle{std::move(handle)},
, m_wait{ m_callback{callback},
::CreateThreadpoolWait(OnEvent, this, NULL), m_wait{::CreateThreadpoolWait(OnEvent, this, NULL),
::CloseThreadpoolWait } ::CloseThreadpoolWait} {
{
::SetThreadpoolWait(m_wait.get(), static_cast<HANDLE>(m_handle), NULL); ::SetThreadpoolWait(m_wait.get(), static_cast<HANDLE>(m_handle), NULL);
} }
ConnectionMonitor::HandleMonitor::Waiter::~Waiter() {
ConnectionMonitor::HandleMonitor::Waiter::~Waiter()
{
::SetThreadpoolWait(m_wait.get(), NULL, NULL); ::SetThreadpoolWait(m_wait.get(), NULL, NULL);
::WaitForThreadpoolWaitCallbacks(m_wait.get(), TRUE); ::WaitForThreadpoolWaitCallbacks(m_wait.get(), TRUE);
} }
VOID CALLBACK ConnectionMonitor::HandleMonitor::Waiter::OnEvent( VOID CALLBACK ConnectionMonitor::HandleMonitor::Waiter::OnEvent(
PTP_CALLBACK_INSTANCE /*instance*/, PTP_CALLBACK_INSTANCE /*instance*/,
PVOID context, PVOID context,
PTP_WAIT /*wait*/, PTP_WAIT /*wait*/,
TP_WAIT_RESULT waitResult) TP_WAIT_RESULT waitResult) {
{ if (waitResult == WAIT_OBJECT_0) {
if (waitResult == WAIT_OBJECT_0)
{
static_cast<Waiter*>(context)->m_callback(); static_cast<Waiter*>(context)->m_callback();
} } else {
else throw std::runtime_error{"Unexpected wait result is received."};
{
throw std::runtime_error{ "Unexpected wait result is received." };
} }
} }

Просмотреть файл

@ -1,33 +1,24 @@
#include "Interprocess/Connection/EndPointInfoUtils.h" #include "Interprocess/Connection/EndPointInfoUtils.h"
#include "Utils/Windows.h"
#include <boost/uuid/random_generator.hpp> #include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid_io.hpp> #include <boost/uuid/uuid_io.hpp>
#include "Utils/Windows.h"
namespace L4 namespace L4 {
{ namespace Interprocess {
namespace Interprocess namespace Connection {
{
namespace Connection
{
// EndPointInfoFactory class implementation. // EndPointInfoFactory class implementation.
EndPointInfo EndPointInfoFactory::Create() const EndPointInfo EndPointInfoFactory::Create() const {
{ return EndPointInfo{GetCurrentProcessId(),
return EndPointInfo{ GetCurrentProcessId(), boost::uuids::random_generator()() }; boost::uuids::random_generator()()};
} }
// StringConverter class implementation. // StringConverter class implementation.
std::string StringConverter::operator()(const EndPointInfo& endPoint) const std::string StringConverter::operator()(const EndPointInfo& endPoint) const {
{ return "[pid:" + std::to_string(endPoint.m_pid) + "," +
return "[pid:" "uuid:" + boost::uuids::to_string(endPoint.m_uuid) + "]";
+ std::to_string(endPoint.m_pid)
+ ","
+ "uuid:"
+ boost::uuids::to_string(endPoint.m_uuid)
+ "]";
} }
} // namespace Connection } // namespace Connection

Просмотреть файл

@ -1,39 +1,26 @@
#include "Interprocess/Utils/Handle.h" #include "Interprocess/Utils/Handle.h"
#include "Utils/Exception.h"
#include <boost/format.hpp> #include <boost/format.hpp>
#include "Utils/Exception.h"
namespace L4 namespace L4 {
{ namespace Interprocess {
namespace Interprocess namespace Utils {
{
namespace Utils
{
// Handle class implementation. // Handle class implementation.
Handle::Handle(HANDLE handle, bool verifyHandle) Handle::Handle(HANDLE handle, bool verifyHandle)
: m_handle{ Verify(handle, verifyHandle), ::CloseHandle } : m_handle{Verify(handle, verifyHandle), ::CloseHandle} {}
{}
Handle::Handle(Handle&& other) : m_handle{std::move(other.m_handle)} {}
Handle::Handle(Handle&& other) Handle::operator HANDLE() const {
: m_handle{ std::move(other.m_handle) }
{}
Handle::operator HANDLE() const
{
return m_handle.get(); return m_handle.get();
} }
HANDLE Handle::Verify(HANDLE handle, bool verifyHandle) const {
HANDLE Handle::Verify(HANDLE handle, bool verifyHandle) const if (handle == NULL || handle == INVALID_HANDLE_VALUE || verifyHandle) {
{
if (handle == NULL || handle == INVALID_HANDLE_VALUE || verifyHandle)
{
auto error = ::GetLastError(); auto error = ::GetLastError();
if (error != ERROR_SUCCESS) if (error != ERROR_SUCCESS) {
{
boost::format err("Invalid handle: %1%."); boost::format err("Invalid handle: %1%.");
err % error; err % error;
throw RuntimeException(err.str()); throw RuntimeException(err.str());

Просмотреть файл

@ -1,21 +1,17 @@
#include "Log/PerfLogger.h" #include "Log/PerfLogger.h"
#include "Utils/Exception.h"
#include <boost/format.hpp> #include <boost/format.hpp>
#include "Utils/Exception.h"
namespace L4 namespace L4 {
{
// PerfData class implementation. // PerfData class implementation.
void PerfData::AddHashTablePerfData(const char* hashTableName, const HashTablePerfData& perfData) void PerfData::AddHashTablePerfData(const char* hashTableName,
{ const HashTablePerfData& perfData) {
auto result = m_hashTablesPerfData.insert( auto result = m_hashTablesPerfData.insert(
std::make_pair( std::make_pair(hashTableName, HashTablesPerfData::mapped_type(perfData)));
hashTableName,
HashTablesPerfData::mapped_type(perfData)));
if (!result.second) if (!result.second) {
{
boost::format err("Duplicate hash table name found: '%1%'."); boost::format err("Duplicate hash table name found: '%1%'.");
err % hashTableName; err % hashTableName;
throw RuntimeException(err.str()); throw RuntimeException(err.str());