Rework SHOW_MEMORY
All checks were successful
Tests / Clang total: 932, passed: 932
Clang |Total|New|Outstanding|Fixed|Trend
|:-:|:-:|:-:|:-:|:-:
|0|0|0|0|:clap:
Tests / Release [gcc] total: 932, passed: 932
Tests / Release [gcc,aarch64] total: 931, passed: 931
Tests / Coverage total: 930, passed: 930
weaselab/conflict-set/pipeline/head This commit looks good
All checks were successful
Tests / Clang total: 932, passed: 932
Clang |Total|New|Outstanding|Fixed|Trend
|:-:|:-:|:-:|:-:|:-:
|0|0|0|0|:clap:
Tests / Release [gcc] total: 932, passed: 932
Tests / Release [gcc,aarch64] total: 931, passed: 931
Tests / Coverage total: 930, passed: 930
weaselab/conflict-set/pipeline/head This commit looks good
closes #10
This commit is contained in:
179
ConflictSet.cpp
179
ConflictSet.cpp
@@ -53,6 +53,21 @@ limitations under the License.
|
|||||||
#define assume assert
|
#define assume assert
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <malloc/malloc.h>
|
||||||
|
#endif
|
||||||
|
void addNode(struct Node *);
|
||||||
|
void removeNode(struct Node *);
|
||||||
|
void addKey(struct Node *);
|
||||||
|
void removeKey(struct Node *);
|
||||||
|
#else
|
||||||
|
constexpr void addNode(struct Node *) {}
|
||||||
|
constexpr void removeNode(struct Node *) {}
|
||||||
|
constexpr void addKey(struct Node *) {}
|
||||||
|
constexpr void removeKey(struct Node *) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
// ==================== BEGIN IMPLEMENTATION ====================
|
// ==================== BEGIN IMPLEMENTATION ====================
|
||||||
|
|
||||||
struct Entry {
|
struct Entry {
|
||||||
@@ -303,10 +318,6 @@ struct BoundedFreeListAllocator {
|
|||||||
static_assert(std::derived_from<T, Node>);
|
static_assert(std::derived_from<T, Node>);
|
||||||
|
|
||||||
T *allocate(int partialKeyCapacity) {
|
T *allocate(int partialKeyCapacity) {
|
||||||
#if SHOW_MEMORY
|
|
||||||
++liveAllocations;
|
|
||||||
maxLiveAllocations = std::max(maxLiveAllocations, liveAllocations);
|
|
||||||
#endif
|
|
||||||
if (freeList != nullptr) {
|
if (freeList != nullptr) {
|
||||||
T *n = (T *)freeList;
|
T *n = (T *)freeList;
|
||||||
VALGRIND_MAKE_MEM_UNDEFINED(n, sizeof(T));
|
VALGRIND_MAKE_MEM_UNDEFINED(n, sizeof(T));
|
||||||
@@ -319,22 +330,22 @@ struct BoundedFreeListAllocator {
|
|||||||
return new (n) T;
|
return new (n) T;
|
||||||
} else {
|
} else {
|
||||||
// The intent is to filter out too-small nodes in the freelist
|
// The intent is to filter out too-small nodes in the freelist
|
||||||
free(n);
|
removeNode(n);
|
||||||
|
safe_free(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *result = new (safe_malloc(sizeof(T) + partialKeyCapacity)) T;
|
auto *result = new (safe_malloc(sizeof(T) + partialKeyCapacity)) T;
|
||||||
result->partialKeyCapacity = partialKeyCapacity;
|
result->partialKeyCapacity = partialKeyCapacity;
|
||||||
|
addNode(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void release(T *p) {
|
void release(T *p) {
|
||||||
#if SHOW_MEMORY
|
|
||||||
--liveAllocations;
|
|
||||||
#endif
|
|
||||||
static_assert(std::is_trivially_destructible_v<T>);
|
static_assert(std::is_trivially_destructible_v<T>);
|
||||||
if (freeListBytes >= kMemoryBound) {
|
if (freeListBytes >= kMemoryBound) {
|
||||||
return free(p);
|
removeNode(p);
|
||||||
|
return safe_free(p);
|
||||||
}
|
}
|
||||||
memcpy((void *)p, &freeList, sizeof(freeList));
|
memcpy((void *)p, &freeList, sizeof(freeList));
|
||||||
freeList = p;
|
freeList = p;
|
||||||
@@ -347,22 +358,14 @@ struct BoundedFreeListAllocator {
|
|||||||
VALGRIND_MAKE_MEM_DEFINED(iter, sizeof(iter));
|
VALGRIND_MAKE_MEM_DEFINED(iter, sizeof(iter));
|
||||||
auto *tmp = iter;
|
auto *tmp = iter;
|
||||||
memcpy(&iter, iter, sizeof(void *));
|
memcpy(&iter, iter, sizeof(void *));
|
||||||
free(tmp);
|
removeNode(((T *)tmp));
|
||||||
|
safe_free(tmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SHOW_MEMORY
|
|
||||||
int64_t highWaterMarkBytes() const { return maxLiveAllocations * sizeof(T); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int64_t freeListBytes = 0;
|
int64_t freeListBytes = 0;
|
||||||
void *freeList = nullptr;
|
void *freeList = nullptr;
|
||||||
#if SHOW_MEMORY
|
|
||||||
// TODO Track partial key bytes
|
|
||||||
int64_t maxLiveAllocations = 0;
|
|
||||||
int64_t liveAllocations = 0;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t *Node::partialKey() {
|
uint8_t *Node::partialKey() {
|
||||||
@@ -821,7 +824,8 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
|||||||
if constexpr (kUseFreeList) {
|
if constexpr (kUseFreeList) {
|
||||||
allocators->node0.release(self0);
|
allocators->node0.release(self0);
|
||||||
} else {
|
} else {
|
||||||
free(self0);
|
removeNode(self0);
|
||||||
|
safe_free(self0);
|
||||||
}
|
}
|
||||||
self = newSelf;
|
self = newSelf;
|
||||||
} break;
|
} break;
|
||||||
@@ -841,7 +845,8 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
|||||||
if constexpr (kUseFreeList) {
|
if constexpr (kUseFreeList) {
|
||||||
allocators->node3.release(self3);
|
allocators->node3.release(self3);
|
||||||
} else {
|
} else {
|
||||||
free(self3);
|
removeNode(self3);
|
||||||
|
safe_free(self3);
|
||||||
}
|
}
|
||||||
self = newSelf;
|
self = newSelf;
|
||||||
} break;
|
} break;
|
||||||
@@ -861,7 +866,8 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
|||||||
if constexpr (kUseFreeList) {
|
if constexpr (kUseFreeList) {
|
||||||
allocators->node16.release(self16);
|
allocators->node16.release(self16);
|
||||||
} else {
|
} else {
|
||||||
free(self16);
|
removeNode(self16);
|
||||||
|
safe_free(self16);
|
||||||
}
|
}
|
||||||
self = newSelf;
|
self = newSelf;
|
||||||
} break;
|
} break;
|
||||||
@@ -885,7 +891,8 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
|||||||
if constexpr (kUseFreeList) {
|
if constexpr (kUseFreeList) {
|
||||||
allocators->node48.release(self48);
|
allocators->node48.release(self48);
|
||||||
} else {
|
} else {
|
||||||
free(self48);
|
removeNode(self48);
|
||||||
|
safe_free(self48);
|
||||||
}
|
}
|
||||||
self = newSelf;
|
self = newSelf;
|
||||||
} break;
|
} break;
|
||||||
@@ -903,7 +910,8 @@ void freeAndMakeCapacityAtLeast(Node *&self, int capacity,
|
|||||||
if constexpr (kUseFreeList) {
|
if constexpr (kUseFreeList) {
|
||||||
allocators->node256.release(self256);
|
allocators->node256.release(self256);
|
||||||
} else {
|
} else {
|
||||||
free(self256);
|
removeNode(self256);
|
||||||
|
safe_free(self256);
|
||||||
}
|
}
|
||||||
self = newSelf;
|
self = newSelf;
|
||||||
} break;
|
} break;
|
||||||
@@ -1072,7 +1080,10 @@ Node *erase(Node *self, NodeAllocators *allocators, ConflictSet::Impl *impl,
|
|||||||
uint8_t parentsIndex = self->parentsIndex;
|
uint8_t parentsIndex = self->parentsIndex;
|
||||||
|
|
||||||
auto *result = nextLogical(self);
|
auto *result = nextLogical(self);
|
||||||
|
|
||||||
|
removeKey(self);
|
||||||
self->entryPresent = false;
|
self->entryPresent = false;
|
||||||
|
|
||||||
if (self->numChildren != 0) {
|
if (self->numChildren != 0) {
|
||||||
const bool update = result == dontInvalidate;
|
const bool update = result == dontInvalidate;
|
||||||
maybeDownsize(self, allocators, impl, result);
|
maybeDownsize(self, allocators, impl, result);
|
||||||
@@ -1969,7 +1980,10 @@ template <bool kBegin>
|
|||||||
memcpy((char *)*self + kNodeCopyBegin, (char *)old + kNodeCopyBegin,
|
memcpy((char *)*self + kNodeCopyBegin, (char *)old + kNodeCopyBegin,
|
||||||
kNodeCopySize);
|
kNodeCopySize);
|
||||||
(*self)->partialKeyLen = partialKeyIndex;
|
(*self)->partialKeyLen = partialKeyIndex;
|
||||||
|
|
||||||
|
// Not necessary to call removeKey here, since this node is "synthetic"
|
||||||
(*self)->entryPresent = false;
|
(*self)->entryPresent = false;
|
||||||
|
|
||||||
(*self)->numChildren = 0;
|
(*self)->numChildren = 0;
|
||||||
memcpy((*self)->partialKey(), old->partialKey(),
|
memcpy((*self)->partialKey(), old->partialKey(),
|
||||||
(*self)->partialKeyLen);
|
(*self)->partialKeyLen);
|
||||||
@@ -2035,6 +2049,14 @@ void destroyTree(Node *root) {
|
|||||||
Arena arena;
|
Arena arena;
|
||||||
auto toFree = vector<Node *>(arena);
|
auto toFree = vector<Node *>(arena);
|
||||||
toFree.push_back(root);
|
toFree.push_back(root);
|
||||||
|
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
for (auto *iter = root; iter != nullptr; iter = nextPhysical(iter)) {
|
||||||
|
removeNode(iter);
|
||||||
|
removeKey(iter);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
while (toFree.size() > 0) {
|
while (toFree.size() > 0) {
|
||||||
auto *n = toFree.back();
|
auto *n = toFree.back();
|
||||||
toFree.pop_back();
|
toFree.pop_back();
|
||||||
@@ -2045,7 +2067,7 @@ void destroyTree(Node *root) {
|
|||||||
assert(c != nullptr);
|
assert(c != nullptr);
|
||||||
toFree.push_back(c);
|
toFree.push_back(c);
|
||||||
}
|
}
|
||||||
free(n);
|
safe_free(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2055,7 +2077,10 @@ void addPointWrite(Node *&root, int64_t oldestVersion,
|
|||||||
auto *n = insert<true>(&root, key, writeVersion, allocators, impl);
|
auto *n = insert<true>(&root, key, writeVersion, allocators, impl);
|
||||||
if (!n->entryPresent) {
|
if (!n->entryPresent) {
|
||||||
auto *p = nextLogical(n);
|
auto *p = nextLogical(n);
|
||||||
|
|
||||||
|
addKey(n);
|
||||||
n->entryPresent = true;
|
n->entryPresent = true;
|
||||||
|
|
||||||
n->entry.pointVersion = writeVersion;
|
n->entry.pointVersion = writeVersion;
|
||||||
maxVersion(n, impl) = writeVersion;
|
maxVersion(n, impl) = writeVersion;
|
||||||
n->entry.rangeVersion =
|
n->entry.rangeVersion =
|
||||||
@@ -2118,6 +2143,8 @@ void addWriteRange(Node *&root, int64_t oldestVersion,
|
|||||||
insert<true>(useAsRoot, begin, writeVersion, allocators, impl);
|
insert<true>(useAsRoot, begin, writeVersion, allocators, impl);
|
||||||
|
|
||||||
const bool insertedBegin = !beginNode->entryPresent;
|
const bool insertedBegin = !beginNode->entryPresent;
|
||||||
|
|
||||||
|
addKey(beginNode);
|
||||||
beginNode->entryPresent = true;
|
beginNode->entryPresent = true;
|
||||||
|
|
||||||
if (insertedBegin) {
|
if (insertedBegin) {
|
||||||
@@ -2136,6 +2163,8 @@ void addWriteRange(Node *&root, int64_t oldestVersion,
|
|||||||
auto *endNode = insert<false>(useAsRoot, end, writeVersion, allocators, impl);
|
auto *endNode = insert<false>(useAsRoot, end, writeVersion, allocators, impl);
|
||||||
|
|
||||||
const bool insertedEnd = !endNode->entryPresent;
|
const bool insertedEnd = !endNode->entryPresent;
|
||||||
|
|
||||||
|
addKey(endNode);
|
||||||
endNode->entryPresent = true;
|
endNode->entryPresent = true;
|
||||||
|
|
||||||
if (insertedEnd) {
|
if (insertedEnd) {
|
||||||
@@ -2302,6 +2331,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
rootMaxVersion = oldestVersion;
|
rootMaxVersion = oldestVersion;
|
||||||
root->entry.pointVersion = oldestVersion;
|
root->entry.pointVersion = oldestVersion;
|
||||||
root->entry.rangeVersion = oldestVersion;
|
root->entry.rangeVersion = oldestVersion;
|
||||||
|
|
||||||
|
addKey(root);
|
||||||
root->entryPresent = true;
|
root->entryPresent = true;
|
||||||
}
|
}
|
||||||
~Impl() { destroyTree(root); }
|
~Impl() { destroyTree(root); }
|
||||||
@@ -2380,7 +2411,7 @@ ConflictSet::ConflictSet(int64_t oldestVersion)
|
|||||||
ConflictSet::~ConflictSet() {
|
ConflictSet::~ConflictSet() {
|
||||||
if (impl) {
|
if (impl) {
|
||||||
impl->~Impl();
|
impl->~Impl();
|
||||||
free(impl);
|
safe_free(impl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2420,7 +2451,7 @@ ConflictSet_create(int64_t oldestVersion) {
|
|||||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||||
using Impl = ConflictSet::Impl;
|
using Impl = ConflictSet::Impl;
|
||||||
((Impl *)cs)->~Impl();
|
((Impl *)cs)->~Impl();
|
||||||
free(cs);
|
safe_free(cs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2652,6 +2683,100 @@ namespace std {
|
|||||||
void __throw_length_error(const char *) { __builtin_unreachable(); }
|
void __throw_length_error(const char *) { __builtin_unreachable(); }
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
|
||||||
|
int64_t nodeBytes = 0;
|
||||||
|
int64_t peakNodeBytes = 0;
|
||||||
|
int64_t partialCapacityBytes = 0;
|
||||||
|
int64_t peakPartialCapacityBytes = 0;
|
||||||
|
int64_t totalKeys = 0;
|
||||||
|
int64_t peakKeys = 0;
|
||||||
|
int64_t keyBytes = 0;
|
||||||
|
int64_t peakKeyBytes = 0;
|
||||||
|
|
||||||
|
int64_t getNodeSize(struct Node *n) {
|
||||||
|
switch (n->type) {
|
||||||
|
case Type_Node0:
|
||||||
|
return sizeof(Node0);
|
||||||
|
case Type_Node3:
|
||||||
|
return sizeof(Node3);
|
||||||
|
case Type_Node16:
|
||||||
|
return sizeof(Node16);
|
||||||
|
case Type_Node48:
|
||||||
|
return sizeof(Node48);
|
||||||
|
case Type_Node256:
|
||||||
|
return sizeof(Node256);
|
||||||
|
}
|
||||||
|
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t getSearchPathLength(Node *n) {
|
||||||
|
assert(n != nullptr);
|
||||||
|
int64_t result = 0;
|
||||||
|
for (;;) {
|
||||||
|
result += n->partialKeyLen;
|
||||||
|
if (n->parent == nullptr) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++result;
|
||||||
|
n = n->parent;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNode(Node *n) {
|
||||||
|
nodeBytes += getNodeSize(n);
|
||||||
|
partialCapacityBytes += n->partialKeyCapacity;
|
||||||
|
if (nodeBytes > peakNodeBytes) {
|
||||||
|
peakNodeBytes = nodeBytes;
|
||||||
|
}
|
||||||
|
if (partialCapacityBytes > peakPartialCapacityBytes) {
|
||||||
|
peakPartialCapacityBytes = partialCapacityBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeNode(Node *n) {
|
||||||
|
nodeBytes -= getNodeSize(n);
|
||||||
|
partialCapacityBytes -= n->partialKeyCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addKey(Node *n) {
|
||||||
|
if (!n->entryPresent) {
|
||||||
|
++totalKeys;
|
||||||
|
keyBytes += getSearchPathLength(n);
|
||||||
|
if (totalKeys > peakKeys) {
|
||||||
|
peakKeys = totalKeys;
|
||||||
|
}
|
||||||
|
if (keyBytes > peakKeyBytes) {
|
||||||
|
peakKeyBytes = keyBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeKey(Node *n) {
|
||||||
|
if (n->entryPresent) {
|
||||||
|
--totalKeys;
|
||||||
|
keyBytes -= getSearchPathLength(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct __attribute__((visibility("default"))) PeakPrinter {
|
||||||
|
~PeakPrinter() {
|
||||||
|
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
|
||||||
|
printf("Node bytes: %g\n", double(nodeBytes));
|
||||||
|
printf("Peak node bytes: %g\n", double(peakNodeBytes));
|
||||||
|
printf("Expected worst case node bytes: %g\n",
|
||||||
|
double(peakKeys * kBytesPerKey));
|
||||||
|
printf("Key bytes: %g\n", double(keyBytes));
|
||||||
|
printf("Peak key bytes: %g (not sharing common prefixes)\n",
|
||||||
|
double(peakKeyBytes));
|
||||||
|
printf("Partial capacity bytes: %g\n", double(partialCapacityBytes));
|
||||||
|
printf("Peak partial key capacity bytes: %g\n",
|
||||||
|
double(peakPartialCapacityBytes));
|
||||||
|
}
|
||||||
|
} peakPrinter;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_MAIN
|
#ifdef ENABLE_MAIN
|
||||||
|
|
||||||
void printTree() {
|
void printTree() {
|
||||||
|
26
Internal.h
26
Internal.h
@@ -40,13 +40,39 @@ operator<=>(const std::span<const uint8_t> &lhs,
|
|||||||
|
|
||||||
// GCOVR_EXCL_START
|
// GCOVR_EXCL_START
|
||||||
|
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <malloc/malloc.h>
|
||||||
|
#endif
|
||||||
|
inline int64_t mallocBytes = 0;
|
||||||
|
inline int64_t peakMallocBytes = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// malloc that aborts on OOM and thus always returns a non-null pointer
|
||||||
__attribute__((always_inline)) inline void *safe_malloc(size_t s) {
|
__attribute__((always_inline)) inline void *safe_malloc(size_t s) {
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
mallocBytes += s;
|
||||||
|
if (mallocBytes > peakMallocBytes) {
|
||||||
|
peakMallocBytes = mallocBytes;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (void *p = malloc(s)) {
|
if (void *p = malloc(s)) {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There's nothing safer about this than free. Only called safe_free for
|
||||||
|
// symmetry with safe_malloc.
|
||||||
|
__attribute__((always_inline)) inline void safe_free(void *p) {
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
#ifdef __APPLE__
|
||||||
|
mallocBytes -= malloc_size(p);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== BEGIN ARENA IMPL ====================
|
// ==================== BEGIN ARENA IMPL ====================
|
||||||
|
|
||||||
/// Group allocations with similar lifetimes to amortize the cost of malloc/free
|
/// Group allocations with similar lifetimes to amortize the cost of malloc/free
|
||||||
|
Reference in New Issue
Block a user