Track mutation versions precisely

This will be necessary for correct garbage collection
This commit is contained in:
2024-05-08 17:59:43 -07:00
parent 4482f93895
commit b5917eb397

View File

@@ -73,15 +73,26 @@ namespace weaselab {
constexpr int kPathLengthUpperBound = 96;
struct Entry {
int64_t insertVersion;
// If there is a point mutation at key, then pointVersion is its version.
// Otherwise it's negative.
int64_t pointVersion;
// If there is a range mutation ending at key, then rangeVersion is its
// version. Otherwise it's negative.
int64_t rangeVersion;
int keyLen;
// Negative if this key is cleared
// Negative if this key is cleared. Only meaningful if this is a point
// mutation.
int valLen;
mutable int refCount;
uint32_t priority;
// True if the entry is a point mutation. If false, this entry's key should be
// read through to the underlying data structure.
bool pointMutation() const { return pointVersion >= 0; }
// True if mutations in (pred, this) are cleared. If false, (pred, this)
// should be read through to the underlying data structure.
bool clearTo;
bool clearTo() const { return rangeVersion >= 0; }
// There's an extra zero byte past the end of getKey, used for
// reconstructing logical mutations without copies.
@@ -102,15 +113,16 @@ struct Entry {
}
}
static Entry *make(int64_t insertVersion, const uint8_t *key, int keyLen,
const uint8_t *val, int valLen, bool clearTo) {
static Entry *make(int64_t pointVersion, int64_t rangeVersion,
const uint8_t *key, int keyLen, const uint8_t *val,
int valLen) {
auto e = (Entry *)malloc(sizeof(Entry) + keyLen + 1 + std::max(valLen, 0));
e->insertVersion = insertVersion;
e->pointVersion = pointVersion;
e->rangeVersion = rangeVersion;
e->keyLen = keyLen;
e->valLen = valLen;
e->refCount = 1;
e->priority = XXH3_64bits(key, keyLen);
e->clearTo = clearTo;
if (keyLen > 0) {
memcpy((uint8_t *)e->getKey(), key, keyLen);
}
@@ -525,8 +537,9 @@ struct VersionedMap::Impl {
return finger;
}
// Infers `val` and `clearTo` if not set
void insert(Key key, std::optional<Val> val, std::optional<bool> clearTo) {
// If `val` is set, then this is a point mutation at `latestVersion`.
// Otherwise it's the end of a range mutation at `latestVersion`.
void insert(Key key, std::optional<Val> val) {
Finger finger;
bool ignored;
finger.push(latestRoot, ignored);
@@ -549,34 +562,36 @@ struct VersionedMap::Impl {
c > 0);
}
// Infer `val` if not set
if (!val.has_value()) {
if (inserted) {
val = {nullptr, -1};
} else {
auto *entry = mm.base[finger.backNode()].entry;
val = {entry->getVal(), entry->valLen};
}
}
// Infer `clearTo` if not set
if (!clearTo.has_value()) {
int64_t pointVersion, rangeVersion;
if (val.has_value()) {
pointVersion = latestVersion;
if (inserted) {
auto copy = finger;
move<std::memory_order_relaxed>(copy, latestVersion, true);
if (copy.searchPathSize() == 0) {
clearTo = false;
rangeVersion = -1;
} else {
clearTo = mm.base[copy.backNode()].entry->clearTo;
rangeVersion = mm.base[copy.backNode()].entry->rangeVersion;
}
} else {
clearTo = false;
auto *entry = mm.base[finger.backNode()].entry;
rangeVersion = entry->rangeVersion;
}
} else {
rangeVersion = latestVersion;
if (inserted) {
val = {nullptr, -1};
pointVersion = -1;
} else {
auto *entry = mm.base[finger.backNode()].entry;
val = {entry->getVal(), entry->valLen};
pointVersion = entry->pointVersion;
}
}
// Prepare new node
uint32_t node =
newNode(latestVersion, key.p, key.len, val->p, val->len, *clearTo);
newNode(pointVersion, rangeVersion, key.p, key.len, val->p, val->len);
if (!inserted) {
auto &n = mm.base[node];
n.pointer[0] = child<std::memory_order_relaxed>(finger.backNode(), false,
@@ -662,24 +677,30 @@ struct VersionedMap::Impl {
finger.setSearchPathSizeUnsafe(oldSize);
if (greaterThan) {
while (auto c = child<std::memory_order_relaxed>(finger.backNode(), false,
latestVersion) != 0) {
uint32_t c;
while ((c = child<std::memory_order_relaxed>(finger.backNode(), false,
latestVersion)) != 0) {
finger.push(c, false);
}
} else {
move<std::memory_order_relaxed>(finger, latestVersion, true);
}
if (finger.backNode() == 0) {
finger.pop();
assert(finger.searchPathSize() == 0);
}
}
uint32_t newNode(int64_t version, const uint8_t *key, int keyLen,
const uint8_t *val, int valLen, bool clearTo) {
uint32_t newNode(int64_t version, int64_t rangeVersion, const uint8_t *key,
int keyLen, const uint8_t *val, int valLen) {
auto result = mm.allocate();
auto &node = mm.base[result];
node.updateVersion = version;
node.pointer[0] = 0;
node.pointer[1] = 0;
node.updated.store(false, std::memory_order_relaxed);
node.entry = Entry::make(version, key, keyLen, val, valLen, clearTo);
node.entry = Entry::make(version, rangeVersion, key, keyLen, val, valLen);
return result;
}
@@ -694,6 +715,7 @@ struct VersionedMap::Impl {
void addMutations(const Mutation *mutations, int numMutations,
int64_t version) {
// TODO scan to remove mutations older than oldestVersion
assert(latestVersion < version);
latestVersion = version;
latestRoot = roots.roots()[roots.rootCount() - 1];
@@ -702,18 +724,18 @@ struct VersionedMap::Impl {
const auto &m = mutations[i];
switch (m.type) {
case Set: {
insert({m.param1, m.param1Len}, {{m.param2, m.param2Len}}, {});
insert({m.param1, m.param1Len}, {{m.param2, m.param2Len}});
} break;
case Clear: {
insert({m.param1, m.param1Len}, {{nullptr, -1}}, {});
insert({m.param1, m.param1Len}, {{nullptr, -1}});
if (m.param2Len > 0) {
auto iter = search({m.param1, m.param1Len}, latestVersion);
move<std::memory_order_relaxed>(iter, latestVersion, true);
while (iter.backNode() != 0 &&
while (iter.searchPathSize() > 0 &&
mm.base[iter.backNode()] < Key{m.param2, m.param2Len}) {
remove(iter);
}
insert({m.param2, m.param2Len}, {}, true);
insert({m.param2, m.param2Len}, {});
}
} break;
default: // GCOVR_EXCL_LINE
@@ -760,6 +782,8 @@ struct VersionedMap::Iterator::Impl {
int64_t version;
VersionedMap::Impl *map;
int cmp;
// True if this is a point mutation and a range mutation, and we're
// materializing the range mutation instead of the point mutation.
bool materializeClearEndingHere = false;
};
@@ -801,7 +825,7 @@ VersionedMap::Mutation VersionedMap::Iterator::operator*() const {
assert(impl->finger.searchPathSize() != 0);
const auto &entry = *impl->map->mm.base[impl->finger.backNode()].entry;
if (impl->materializeClearEndingHere) {
assert(entry.clearTo);
assert(entry.pointMutation() && entry.clearTo());
auto prev = *this;
--prev;
const auto &prevEntry =
@@ -819,7 +843,7 @@ VersionedMap::Mutation VersionedMap::Iterator::operator*() const {
VersionedMap::Iterator &VersionedMap::Iterator::operator++() {
const auto &entry = *impl->map->mm.base[impl->finger.backNode()].entry;
if (impl->materializeClearEndingHere) {
assert(entry.clearTo);
assert(entry.pointMutation() && entry.clearTo());
impl->materializeClearEndingHere = false;
return *this;
}
@@ -827,7 +851,8 @@ VersionedMap::Iterator &VersionedMap::Iterator::operator++() {
impl->map->move<std::memory_order_acquire>(impl->finger, impl->version, true);
impl->materializeClearEndingHere =
impl->finger.searchPathSize() > 0 &&
impl->map->mm.base[impl->finger.backNode()].entry->clearTo;
impl->map->mm.base[impl->finger.backNode()].entry->pointMutation() &&
impl->map->mm.base[impl->finger.backNode()].entry->clearTo();
return *this;
}
@@ -840,7 +865,8 @@ VersionedMap::Iterator VersionedMap::Iterator::operator++(int) {
VersionedMap::Iterator &VersionedMap::Iterator::operator--() {
const auto &entry = *impl->map->mm.base[impl->finger.backNode()].entry;
if (entry.clearTo && !impl->materializeClearEndingHere) {
if (entry.pointMutation() && entry.clearTo() &&
!impl->materializeClearEndingHere) {
impl->materializeClearEndingHere = true;
return *this;
}
@@ -935,7 +961,7 @@ void VersionedMap::Impl::printInOrderHelper(int64_t version, uint32_t node,
} else {
printf(" <cleared>");
}
if (mm.base[node].entry->clearTo) {
if (mm.base[node].entry->clearTo()) {
printf(" <clearTo>");
}
printf("\n");
@@ -1016,50 +1042,6 @@ int main() {
}
}
return 0;
ankerl::nanobench::Bench bench;
bench.minEpochIterations(10000);
weaselab::MemManager mm;
bench.run("allocate", [&]() {
auto x = mm.allocate();
mm.base[x].pointer[0] = 0;
mm.base[x].pointer[1] = 0;
mm.base[x].updated.store(false, std::memory_order_relaxed);
});
mm.gc(nullptr, 0, 0);
for (int i = 0; i < 10000; ++i) {
auto x = mm.allocate();
mm.base[x].pointer[0] = 0;
mm.base[x].pointer[1] = 0;
mm.base[x].updated.store(false, std::memory_order_relaxed);
}
auto root = mm.allocate();
mm.base[root].entry = weaselab::Entry::make(0, nullptr, 0, nullptr, 0,
weaselab::VersionedMap::Set);
mm.base[root].pointer[0] = 0;
mm.base[root].pointer[1] = 0;
mm.base[root].updated.store(false, std::memory_order_relaxed);
bench.run("gc", [&]() { mm.gc(&root, 1, 0); });
{
int i = 0;
constexpr int kNumVersions = 1000;
RootSet roots;
for (; i < kNumVersions; i += 2) {
roots.add(i, i);
roots.add(i, i + 1);
}
bench.run("roots - setOldestVersion", [&]() {
roots.add(i, i);
roots.setOldestVersion(i - kNumVersions);
++i;
});
bench.run("roots - rootForVersion", [&]() {
bench.doNotOptimizeAway(
roots.getThreadSafeHandle().rootForVersion(i - kNumVersions / 2));
});
}
}
#endif