Add std::unordered_map implementation
As a rough upper bound for point query throughput
This commit is contained in:
141
HashTable.cpp
Normal file
141
HashTable.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include "ConflictSet.h"
|
||||
#include "Internal.h"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
// This implementation isn't correct for range queries :). It's just intended as
|
||||
// a reference for performance comparison with point queries.
|
||||
|
||||
// struct is from "https://www.cppstories.com/2021/heterogeneous-access-cpp20/"
|
||||
struct string_hash {
|
||||
using is_transparent = void;
|
||||
[[nodiscard]] size_t operator()(std::string_view txt) const {
|
||||
return std::hash<std::string_view>{}(txt);
|
||||
}
|
||||
[[nodiscard]] size_t operator()(const std::string &txt) const {
|
||||
return std::hash<std::string>{}(txt);
|
||||
}
|
||||
};
|
||||
|
||||
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
Impl(int64_t oldestVersion) : oldestVersion(oldestVersion) {}
|
||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||
int count) const {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto key =
|
||||
std::string_view((const char *)reads[i].begin.p, reads[i].begin.len);
|
||||
auto version = reads[i].readVersion;
|
||||
if (version < oldestVersion) {
|
||||
results[i] = TooOld;
|
||||
continue;
|
||||
}
|
||||
auto iter = map.find(key);
|
||||
results[i] =
|
||||
iter == map.end() || iter->second <= version ? Commit : Conflict;
|
||||
}
|
||||
}
|
||||
|
||||
void addWrites(const ConflictSet::WriteRange *writes, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto &max = map[std::string((const char *)writes[i].begin.p,
|
||||
writes[i].begin.len)];
|
||||
max = std::max(max, writes[i].writeVersion);
|
||||
keyUpdates += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void setOldestVersion(int64_t oldestVersion) {
|
||||
if (oldestVersion <= this->oldestVersion) {
|
||||
return;
|
||||
}
|
||||
this->oldestVersion = oldestVersion;
|
||||
if (keyUpdates < 100) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (; keyUpdates > 0 && removalIter != map.end(); --keyUpdates) {
|
||||
if (removalIter->second <= oldestVersion) {
|
||||
removalIter = map.erase(removalIter);
|
||||
} else {
|
||||
++removalIter;
|
||||
}
|
||||
}
|
||||
|
||||
if (removalIter == map.end()) {
|
||||
removalIter = map.begin();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t keyUpdates = 0;
|
||||
int64_t oldestVersion;
|
||||
std::unordered_map<std::string, int64_t, string_hash, std::equal_to<>> map;
|
||||
|
||||
// This iterator outliving the call to setOldestVersion is only safe because
|
||||
// we only erase from within setOldestVersion
|
||||
decltype(map.begin()) removalIter;
|
||||
};
|
||||
|
||||
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||
int count) const {
|
||||
return impl->check(reads, results, count);
|
||||
}
|
||||
|
||||
void ConflictSet::addWrites(const WriteRange *writes, int count) {
|
||||
return impl->addWrites(writes, count);
|
||||
}
|
||||
|
||||
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
||||
return impl->setOldestVersion(oldestVersion);
|
||||
}
|
||||
|
||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||
: impl(new (safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
||||
|
||||
ConflictSet::~ConflictSet() {
|
||||
if (impl) {
|
||||
impl->~Impl();
|
||||
free(impl);
|
||||
}
|
||||
}
|
||||
|
||||
ConflictSet::ConflictSet(ConflictSet &&other) noexcept
|
||||
: impl(std::exchange(other.impl, nullptr)) {}
|
||||
|
||||
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
||||
impl = std::exchange(other.impl, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
using ConflictSet_Result = ConflictSet::Result;
|
||||
using ConflictSet_Key = ConflictSet::Key;
|
||||
using ConflictSet_ReadRange = ConflictSet::ReadRange;
|
||||
using ConflictSet_WriteRange = ConflictSet::WriteRange;
|
||||
|
||||
extern "C" {
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
|
||||
ConflictSet_Result *results, int count) {
|
||||
((ConflictSet::Impl *)cs)->check(reads, results, count);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes,
|
||||
int count) {
|
||||
((ConflictSet::Impl *)cs)->addWrites(writes, count);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
||||
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void *
|
||||
ConflictSet_create(int64_t oldestVersion) {
|
||||
return new (safe_malloc(sizeof(ConflictSet::Impl)))
|
||||
ConflictSet::Impl{oldestVersion};
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||
using Impl = ConflictSet::Impl;
|
||||
((Impl *)cs)->~Impl();
|
||||
free(cs);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user