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:
parent
072bc86bb1
commit
735c5697d0
|
@ -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();
|
||||||
|
}
|
||||||
|
|
12
flow/Arena.h
12
flow/Arena.h
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue