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

closes #10
This commit is contained in:
2024-03-13 16:48:28 -07:00
parent 351ff3df3b
commit 3cb0765fdd
2 changed files with 178 additions and 27 deletions

View File

@@ -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() {

View File

@@ -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