Added fast size estimates to Arena which are O(1) and are usually accurate. When inaccurate, a full size scan will update all estimates in the tree.

This commit is contained in:
Steve Atherton 2022-02-18 03:25:28 -08:00
parent 072bc86bb1
commit 735c5697d0
2 changed files with 95 additions and 10 deletions

View File

@ -106,15 +106,22 @@ void* Arena::allocate4kAlignedBuffer(uint32_t size) {
return ArenaBlock::dependOn4kAlignedBuffer(impl, size); return ArenaBlock::dependOn4kAlignedBuffer(impl, size);
} }
size_t Arena::getSize() const { size_t Arena::getSize(bool fastInaccurateEstimate) const {
if (impl) { if (impl) {
allowAccess(impl.getPtr()); allowAccess(impl.getPtr());
auto result = impl->totalSize(); size_t result;
if (fastInaccurateEstimate) {
result = impl->estimatedTotalSize();
} else {
result = impl->totalSize();
}
disallowAccess(impl.getPtr()); disallowAccess(impl.getPtr());
return result; return result;
} }
return 0; return 0;
} }
bool Arena::hasFree(size_t size, const void* address) { bool Arena::hasFree(size_t size, const void* address) {
if (impl) { if (impl) {
allowAccess(impl.getPtr()); allowAccess(impl.getPtr());
@ -167,28 +174,38 @@ const void* ArenaBlock::getData() const {
const void* ArenaBlock::getNextData() const { const void* ArenaBlock::getNextData() const {
return (const uint8_t*)getData() + used(); return (const uint8_t*)getData() + used();
} }
size_t ArenaBlock::totalSize() {
size_t ArenaBlock::totalSize() const {
if (isTiny()) { if (isTiny()) {
return size(); return size();
} }
size_t s = size(); // Walk the entire tree to get an accurate size and store it in the estimate for
// each block, recursively.
totalSizeEstimate = size();
int o = nextBlockOffset; int o = nextBlockOffset;
while (o) { while (o) {
ArenaBlockRef* r = (ArenaBlockRef*)((char*)getData() + o); ArenaBlockRef* r = (ArenaBlockRef*)((char*)getData() + o);
makeDefined(r, sizeof(ArenaBlockRef)); makeDefined(r, sizeof(ArenaBlockRef));
if (r->aligned4kBufferSize != 0) { if (r->aligned4kBufferSize != 0) {
s += r->aligned4kBufferSize; totalSizeEstimate += r->aligned4kBufferSize;
} else { } else {
allowAccess(r->next); allowAccess(r->next);
s += r->next->totalSize(); totalSizeEstimate += r->next->totalSize();
disallowAccess(r->next); disallowAccess(r->next);
} }
o = r->nextBlockOffset; o = r->nextBlockOffset;
makeNoAccess(r, sizeof(ArenaBlockRef)); makeNoAccess(r, sizeof(ArenaBlockRef));
} }
return s; return totalSizeEstimate;
} }
size_t ArenaBlock::estimatedTotalSize() const {
if (isTiny()) {
return size();
}
return totalSizeEstimate;
}
// just for debugging: // just for debugging:
void ArenaBlock::getUniqueBlocks(std::set<ArenaBlock*>& a) { void ArenaBlock::getUniqueBlocks(std::set<ArenaBlock*>& a) {
a.insert(this); a.insert(this);
@ -232,6 +249,7 @@ void ArenaBlock::makeReference(ArenaBlock* next) {
makeNoAccess(r, sizeof(ArenaBlockRef)); makeNoAccess(r, sizeof(ArenaBlockRef));
nextBlockOffset = bigUsed; nextBlockOffset = bigUsed;
bigUsed += sizeof(ArenaBlockRef); bigUsed += sizeof(ArenaBlockRef);
totalSizeEstimate += next->estimatedTotalSize();
} }
void* ArenaBlock::make4kAlignedBuffer(uint32_t size) { void* ArenaBlock::make4kAlignedBuffer(uint32_t size) {
@ -245,6 +263,7 @@ void* ArenaBlock::make4kAlignedBuffer(uint32_t size) {
makeNoAccess(r, sizeof(ArenaBlockRef)); makeNoAccess(r, sizeof(ArenaBlockRef));
nextBlockOffset = bigUsed; nextBlockOffset = bigUsed;
bigUsed += sizeof(ArenaBlockRef); bigUsed += sizeof(ArenaBlockRef);
totalSizeEstimate += size;
return result; return result;
} }
@ -341,6 +360,7 @@ ArenaBlock* ArenaBlock::create(int dataSize, Reference<ArenaBlock>& next) {
b->bigSize = 8192; b->bigSize = 8192;
INSTRUMENT_ALLOCATE("Arena8192"); INSTRUMENT_ALLOCATE("Arena8192");
} }
b->totalSizeEstimate = b->bigSize;
b->tinySize = b->tinyUsed = NOT_TINY; b->tinySize = b->tinyUsed = NOT_TINY;
b->bigUsed = sizeof(ArenaBlock); b->bigUsed = sizeof(ArenaBlock);
} else { } else {
@ -350,6 +370,7 @@ ArenaBlock* ArenaBlock::create(int dataSize, Reference<ArenaBlock>& next) {
b = (ArenaBlock*)new uint8_t[reqSize]; b = (ArenaBlock*)new uint8_t[reqSize];
b->tinySize = b->tinyUsed = NOT_TINY; b->tinySize = b->tinyUsed = NOT_TINY;
b->bigSize = reqSize; b->bigSize = reqSize;
b->totalSizeEstimate = b->bigSize;
b->bigUsed = sizeof(ArenaBlock); b->bigUsed = sizeof(ArenaBlock);
if (FLOW_KNOBS && g_allocation_tracing_disabled == 0 && if (FLOW_KNOBS && g_allocation_tracing_disabled == 0 &&
@ -649,4 +670,60 @@ TEST_CASE("/flow/Arena/DefaultBoostHash") {
ASSERT(hashFunc(d) == hashFunc(d)); ASSERT(hashFunc(d) == hashFunc(d));
return Void(); return Void();
} }
TEST_CASE("/flow/Arena/Size") {
Arena a;
// Size estimates are accurate unless dependencies are added to an Arena via another Arena
// handle which points to a non-root node.
//
// Note that the ASSERT argument order matters, the estimate must be calculated first as
// the full accurate calculation will update the estimate
makeString(40, a);
ASSERT_EQ(a.getSize(true), a.getSize());
makeString(700, a);
ASSERT_EQ(a.getSize(true), a.getSize());
// Copy a at a point where it points to a large block with room for block references
Arena b = a;
// copy a at a point where there isn't room for more block references
makeString(1000, a);
Arena c = a;
makeString(1000, a);
makeString(1000, a);
ASSERT_EQ(a.getSize(true), a.getSize());
Standalone<StringRef> s = makeString(500);
a.dependsOn(s.arena());
ASSERT_EQ(a.getSize(true), a.getSize());
Standalone<StringRef> s2 = makeString(500);
a.dependsOn(s2.arena());
ASSERT_EQ(a.getSize(true), a.getSize());
// Add a dependency to b, which will fit in b's root and update b's size estimate
Standalone<StringRef> s3 = makeString(100);
b.dependsOn(s3.arena());
ASSERT_EQ(b.getSize(true), b.getSize());
// But now a's size estimate is out of date because the new reference in b's root is still
// in a's tree
ASSERT_LT(a.getSize(true), a.getSize());
// Now that a full size calc has been done on a, the estimate is up to date.
ASSERT_EQ(a.getSize(true), a.getSize());
// Add a dependency to c, which will NOT fit in c's root, so it will be added to a new
// root for c and that root will not be in a's tree so a's size and estimate remain
// unchanged and the same. The size and estimate of c will also match.
Standalone<StringRef> s4 = makeString(100);
c.dependsOn(s4.arena());
ASSERT_EQ(c.getSize(true), c.getSize());
ASSERT_EQ(a.getSize(true), a.getSize());
return Void();
}

View File

@ -104,7 +104,13 @@ public:
void dependsOn(const Arena& p); void dependsOn(const Arena& p);
void* allocate4kAlignedBuffer(uint32_t size); void* allocate4kAlignedBuffer(uint32_t size);
size_t getSize() const;
// If fastInaccurateEstimate is true this operation is O(1) but it is inaccurate in that it
// will omit memory added to this Arena's block tree using Arena handles which reference
// non-root nodes in this Arena's block tree.
// When fastInaccurateEstimate is false, all estimates in the block tree will be updated to
// be accurate.
size_t getSize(bool fastInaccurateEstimate = false) const;
bool hasFree(size_t size, const void* address); bool hasFree(size_t size, const void* address);
@ -156,6 +162,7 @@ struct ArenaBlock : NonCopyable, ThreadSafeReferenceCounted<ArenaBlock> {
// if tinySize != NOT_TINY, following variables aren't used // if tinySize != NOT_TINY, following variables aren't used
uint32_t bigSize, bigUsed; // include block header uint32_t bigSize, bigUsed; // include block header
uint32_t nextBlockOffset; uint32_t nextBlockOffset;
mutable size_t totalSizeEstimate; // Estimate of the minimum total size of arena blocks this one reaches
void addref(); void addref();
void delref(); void delref();
@ -165,7 +172,8 @@ struct ArenaBlock : NonCopyable, ThreadSafeReferenceCounted<ArenaBlock> {
int unused() const; int unused() const;
const void* getData() const; const void* getData() const;
const void* getNextData() const; const void* getNextData() const;
size_t totalSize(); size_t totalSize() const;
size_t estimatedTotalSize() const;
// just for debugging: // just for debugging:
void getUniqueBlocks(std::set<ArenaBlock*>& a); void getUniqueBlocks(std::set<ArenaBlock*>& a);
int addUsed(int bytes); int addUsed(int bytes);