Commit 2bcb6e69 authored by Kenton Varda's avatar Kenton Varda

Simplify reuse strategy, add reuse comparisons to benchmarks.

parent 15aa868b
......@@ -42,16 +42,6 @@ ReaderArena::ReaderArena(MessageReader* message)
ReaderArena::~ReaderArena() {}
void ReaderArena::reset() {
readLimiter.reset(message->getOptions().traversalLimitInWords * WORDS);
ignoreErrors = false;
segment0.~SegmentReader();
new(&segment0) SegmentReader(this, SegmentId(0), this->message->getSegment(0), &readLimiter);
// TODO: Reuse the rest of the SegmentReaders?
moreSegments = nullptr;
}
SegmentReader* ReaderArena::tryGetSegment(SegmentId id) {
if (id == SegmentId(0)) {
if (segment0.getArray() == nullptr) {
......@@ -110,16 +100,6 @@ BuilderArena::BuilderArena(MessageBuilder* message)
: message(message), segment0(nullptr, SegmentId(0), nullptr, nullptr) {}
BuilderArena::~BuilderArena() {}
void BuilderArena::reset() {
segment0.reset();
if (moreSegments != nullptr) {
// TODO: As mentioned in another TODO below, only the last segment will only be reused.
for (auto& segment: moreSegments->builders) {
segment->reset();
}
}
}
SegmentBuilder* BuilderArena::getSegment(SegmentId id) {
// This method is allowed to crash if the segment ID is not valid.
if (id == SegmentId(0)) {
......
......@@ -164,8 +164,6 @@ public:
~ReaderArena();
CAPNPROTO_DISALLOW_COPY(ReaderArena);
void reset();
// implements Arena ------------------------------------------------
SegmentReader* tryGetSegment(SegmentId id) override;
void reportInvalidData(const char* description) override;
......@@ -189,9 +187,6 @@ public:
~BuilderArena();
CAPNPROTO_DISALLOW_COPY(BuilderArena);
void reset();
// Resets all the segments to be empty, so that a new message can be started.
SegmentBuilder* getSegment(SegmentId id);
// Get the segment with the given id. Crashes or throws an exception if no such segment exists.
......
......@@ -38,10 +38,6 @@ MessageReader::~MessageReader() {
}
}
void MessageReader::reset() {
if (allocatedArena) arena()->reset();
}
internal::StructReader MessageReader::getRoot(const word* defaultValue) {
if (!allocatedArena) {
static_assert(sizeof(internal::ReaderArena) <= sizeof(arenaSpace),
......@@ -71,35 +67,35 @@ MessageBuilder::~MessageBuilder() {
}
}
internal::SegmentBuilder* MessageBuilder::allocateRootSegment() {
if (!allocatedArena) {
internal::SegmentBuilder* MessageBuilder::getRootSegment() {
if (allocatedArena) {
return arena()->getSegment(SegmentId(0));
} else {
static_assert(sizeof(internal::BuilderArena) <= sizeof(arenaSpace),
"arenaSpace is too small to hold a BuilderArena. Please increase it. This will break "
"ABI compatibility.");
new(arena()) internal::BuilderArena(this);
allocatedArena = true;
}
WordCount refSize = 1 * REFERENCES * WORDS_PER_REFERENCE;
internal::SegmentBuilder* segment = arena()->getSegmentWithAvailable(refSize);
CAPNPROTO_ASSERT(segment->getSegmentId() == SegmentId(0),
"First allocated word of new arena was not in segment ID 0.");
word* location = segment->allocate(refSize);
CAPNPROTO_ASSERT(location == segment->getPtrUnchecked(0 * WORDS),
"First allocated word of new arena was not the first word in its segment.");
return segment;
WordCount refSize = 1 * REFERENCES * WORDS_PER_REFERENCE;
internal::SegmentBuilder* segment = arena()->getSegmentWithAvailable(refSize);
CAPNPROTO_ASSERT(segment->getSegmentId() == SegmentId(0),
"First allocated word of new arena was not in segment ID 0.");
word* location = segment->allocate(refSize);
CAPNPROTO_ASSERT(location == segment->getPtrUnchecked(0 * WORDS),
"First allocated word of new arena was not the first word in its segment.");
return segment;
}
}
internal::StructBuilder MessageBuilder::initRoot(const word* defaultValue) {
if (allocatedArena) arena()->reset();
internal::SegmentBuilder* rootSegment = allocateRootSegment();
internal::SegmentBuilder* rootSegment = getRootSegment();
return internal::StructBuilder::initRoot(
rootSegment, rootSegment->getPtrUnchecked(0 * WORDS), defaultValue);
}
internal::StructBuilder MessageBuilder::getRoot(const word* defaultValue) {
internal::SegmentBuilder* rootSegment = allocatedArena ?
arena()->getSegment(SegmentId(0)) : allocateRootSegment();
internal::SegmentBuilder* rootSegment = getRootSegment();
return internal::StructBuilder::getRoot(
rootSegment, rootSegment->getPtrUnchecked(0 * WORDS), defaultValue);
}
......@@ -200,10 +196,15 @@ struct MallocMessageBuilder::MoreSegments {
MallocMessageBuilder::MallocMessageBuilder(
uint firstSegmentWords, AllocationStrategy allocationStrategy)
: nextSize(firstSegmentWords), allocationStrategy(allocationStrategy),
firstSegment(nullptr) {}
ownFirstSegment(true), firstSegment(nullptr) {}
MallocMessageBuilder::MallocMessageBuilder(
ArrayPtr<word> firstSegment, AllocationStrategy allocationStrategy)
: nextSize(firstSegment.size()), allocationStrategy(allocationStrategy),
ownFirstSegment(false), firstSegment(firstSegment.begin()) {}
MallocMessageBuilder::~MallocMessageBuilder() {
free(firstSegment);
if (ownFirstSegment) free(firstSegment);
if (moreSegments != nullptr) {
for (void* ptr: moreSegments->segments) {
free(ptr);
......@@ -212,6 +213,19 @@ MallocMessageBuilder::~MallocMessageBuilder() {
}
ArrayPtr<word> MallocMessageBuilder::allocateSegment(uint minimumSize) {
if (!ownFirstSegment) {
ArrayPtr<word> result = arrayPtr(reinterpret_cast<word*>(firstSegment), nextSize);
firstSegment = nullptr;
ownFirstSegment = true;
if (result.size() >= minimumSize) {
memset(result.begin(), 0, result.size() * sizeof(word));
return result;
}
// If the provided first segment wasn't big enough, we discard it and proceed to allocate
// our own. This never happens in practice since minimumSize is always 1 for the first
// segment.
}
uint size = std::max(minimumSize, nextSize);
void* result = calloc(size, sizeof(word));
......
......@@ -133,14 +133,6 @@ public:
template <typename RootType>
typename RootType::Reader getRoot();
protected:
void reset();
// Clear the cached segment table so that the reader can be reused to read another message.
// reset() may call getSegment() again before returning, so you must arrange for the new segment
// set to be active *before* calling this.
//
// This invalidates any Readers currently pointing into this message.
private:
ReaderOptions options;
......@@ -182,7 +174,7 @@ private:
bool allocatedArena = false;
internal::BuilderArena* arena() { return reinterpret_cast<internal::BuilderArena*>(arenaSpace); }
internal::SegmentBuilder* allocateRootSegment();
internal::SegmentBuilder* getRootSegment();
internal::StructBuilder initRoot(const word* defaultValue);
internal::StructBuilder getRoot(const word* defaultValue);
};
......@@ -232,7 +224,7 @@ private:
ArrayPtr<const ArrayPtr<const word>> segments;
};
enum class AllocationStrategy {
enum class AllocationStrategy: uint8_t {
FIXED_SIZE,
// The builder will prefer to allocate the same amount of space for each segment with no
// heuristic growth. It will still allocate larger segments when the preferred size is too small
......@@ -257,7 +249,7 @@ class MallocMessageBuilder: public MessageBuilder {
// a specific location in memory.
public:
MallocMessageBuilder(uint firstSegmentWords = 1024,
explicit MallocMessageBuilder(uint firstSegmentWords = 1024,
AllocationStrategy allocationStrategy = SUGGESTED_ALLOCATION_STRATEGY);
// Creates a BuilderContext which allocates at least the given number of words for the first
// segment, and then uses the given strategy to decide how much to allocate for subsequent
......@@ -271,6 +263,12 @@ public:
// The defaults have been chosen to be reasonable for most people, so don't change them unless you
// have reason to believe you need to.
explicit MallocMessageBuilder(ArrayPtr<word> firstSegment,
AllocationStrategy allocationStrategy = SUGGESTED_ALLOCATION_STRATEGY);
// This version always returns the given array for the first segment, and then proceeds with the
// allocation strategy. This is useful for optimization when building lots of small messages in
// a tight loop: you can reuse the space for the first segment.
CAPNPROTO_DISALLOW_COPY(MallocMessageBuilder);
virtual ~MallocMessageBuilder();
......@@ -280,6 +278,7 @@ private:
uint nextSize;
AllocationStrategy allocationStrategy;
bool ownFirstSegment;
void* firstSegment;
struct MoreSegments;
......
......@@ -83,7 +83,7 @@ TEST(Serialize, FlatArrayOddSegmentCount) {
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, FlatArrayEventSegmentCount) {
TEST(Serialize, FlatArrayEvenSegmentCount) {
TestMessageBuilder builder(10);
initTestMessage(builder.initRoot<TestAllTypes>());
......@@ -95,25 +95,24 @@ TEST(Serialize, FlatArrayEventSegmentCount) {
class TestInputStream: public InputStream {
public:
TestInputStream(ArrayPtr<const word> data)
TestInputStream(ArrayPtr<const word> data, bool lazy)
: pos(reinterpret_cast<const char*>(data.begin())),
end(reinterpret_cast<const char*>(data.end())) {}
end(reinterpret_cast<const char*>(data.end())),
lazy(lazy) {}
~TestInputStream() {}
bool read(void* buffer, size_t size) override {
if (size_t(end - pos) < size) {
ADD_FAILURE() << "Overran end of stream.";
return false;
} else {
memcpy(buffer, pos, size);
pos += size;
return true;
}
size_t read(void* buffer, size_t minBytes, size_t maxBytes) override {
CAPNPROTO_ASSERT(maxBytes <= size_t(end - pos), "Overran end of stream.");
size_t amount = lazy ? minBytes : maxBytes;
memcpy(buffer, pos, amount);
pos += amount;
return amount;
}
private:
const char* pos;
const char* end;
bool lazy;
};
TEST(Serialize, InputStream) {
......@@ -122,162 +121,81 @@ TEST(Serialize, InputStream) {
Array<word> serialized = messageToFlatArray(builder);
TestInputStream stream(serialized.asPtr());
InputStreamMessageReader reader(&stream, ReaderOptions(), InputStrategy::EAGER);
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, InputStreamLazy) {
TestMessageBuilder builder(1);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputStream stream(serialized.asPtr());
InputStreamMessageReader reader(&stream, ReaderOptions(), InputStrategy::LAZY);
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, InputStreamOddSegmentCount) {
TestMessageBuilder builder(7);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputStream stream(serialized.asPtr());
InputStreamMessageReader reader(&stream, ReaderOptions(), InputStrategy::EAGER);
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, InputStreamOddSegmentCountLazy) {
TestMessageBuilder builder(7);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputStream stream(serialized.asPtr());
InputStreamMessageReader reader(&stream, ReaderOptions(), InputStrategy::LAZY);
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, InputStreamEventSegmentCount) {
TestMessageBuilder builder(10);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputStream stream(serialized.asPtr());
InputStreamMessageReader reader(&stream, ReaderOptions(), InputStrategy::EAGER);
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, InputStreamEventSegmentCountLazy) {
TestMessageBuilder builder(10);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputStream stream(serialized.asPtr());
InputStreamMessageReader reader(&stream, ReaderOptions(), InputStrategy::LAZY);
TestInputStream stream(serialized.asPtr(), false);
InputStreamMessageReader reader(stream, ReaderOptions());
checkTestMessage(reader.getRoot<TestAllTypes>());
}
class TestInputFile: public InputFile {
public:
TestInputFile(ArrayPtr<const word> data)
: begin(reinterpret_cast<const char*>(data.begin())),
size_(data.size() * sizeof(word)) {}
~TestInputFile() {}
bool read(size_t offset, void* buffer, size_t size) override {
if (size_ < offset + size) {
ADD_FAILURE() << "Overran end of file.";
return false;
} else {
memcpy(buffer, begin + offset, size);
return true;
}
}
private:
const char* begin;
size_t size_;
};
TEST(Serialize, InputFile) {
TEST(Serialize, InputStreamScratchSpace) {
TestMessageBuilder builder(1);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputFile file(serialized.asPtr());
InputFileMessageReader reader(&file, ReaderOptions(), InputStrategy::EAGER);
word scratch[4096];
TestInputStream stream(serialized.asPtr(), false);
InputStreamMessageReader reader(stream, ReaderOptions(), ArrayPtr<word>(scratch, 4096));
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, InputFileLazy) {
TEST(Serialize, InputStreamLazy) {
TestMessageBuilder builder(1);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputFile file(serialized.asPtr());
InputFileMessageReader reader(&file, ReaderOptions(), InputStrategy::LAZY);
TestInputStream stream(serialized.asPtr(), true);
InputStreamMessageReader reader(stream, ReaderOptions());
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, InputFileOddSegmentCount) {
TEST(Serialize, InputStreamOddSegmentCount) {
TestMessageBuilder builder(7);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputFile file(serialized.asPtr());
InputFileMessageReader reader(&file, ReaderOptions(), InputStrategy::EAGER);
TestInputStream stream(serialized.asPtr(), false);
InputStreamMessageReader reader(stream, ReaderOptions());
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, InputFileOddSegmentCountLazy) {
TEST(Serialize, InputStreamOddSegmentCountLazy) {
TestMessageBuilder builder(7);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputFile file(serialized.asPtr());
InputFileMessageReader reader(&file, ReaderOptions(), InputStrategy::LAZY);
TestInputStream stream(serialized.asPtr(), true);
InputStreamMessageReader reader(stream, ReaderOptions());
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, InputFileEventSegmentCount) {
TEST(Serialize, InputStreamEvenSegmentCount) {
TestMessageBuilder builder(10);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputFile file(serialized.asPtr());
InputFileMessageReader reader(&file, ReaderOptions(), InputStrategy::EAGER);
TestInputStream stream(serialized.asPtr(), false);
InputStreamMessageReader reader(stream, ReaderOptions());
checkTestMessage(reader.getRoot<TestAllTypes>());
}
TEST(Serialize, InputFileEventSegmentCountLazy) {
TEST(Serialize, InputStreamEvenSegmentCountLazy) {
TestMessageBuilder builder(10);
initTestMessage(builder.initRoot<TestAllTypes>());
Array<word> serialized = messageToFlatArray(builder);
TestInputFile file(serialized.asPtr());
InputFileMessageReader reader(&file, ReaderOptions(), InputStrategy::LAZY);
TestInputStream stream(serialized.asPtr(), true);
InputStreamMessageReader reader(stream, ReaderOptions());
checkTestMessage(reader.getRoot<TestAllTypes>());
}
......@@ -324,7 +242,7 @@ TEST(Serialize, WriteMessageOddSegmentCount) {
EXPECT_TRUE(output.dataEquals(serialized.asPtr()));
}
TEST(Serialize, WriteMessageEventSegmentCount) {
TEST(Serialize, WriteMessageEvenSegmentCount) {
TestMessageBuilder builder(10);
initTestMessage(builder.initRoot<TestAllTypes>());
......@@ -363,22 +281,10 @@ TEST(Serialize, FileDescriptors) {
checkTestMessage(reader.getRoot<TestAllTypes>());
}
{
FileFdMessageReader reader(tmpfile.get(), 0);
checkTestMessage(reader.getRoot<TestAllTypes>());
}
size_t secondStart = lseek(tmpfile, 0, SEEK_CUR);
{
StreamFdMessageReader reader(tmpfile.get());
EXPECT_EQ("second message in file", reader.getRoot<TestAllTypes>().getTextField());
}
{
FileFdMessageReader reader(move(tmpfile), secondStart);
EXPECT_EQ("second message in file", reader.getRoot<TestAllTypes>().getTextField());
}
}
// TODO: Test error cases.
......
This diff is collapsed.
This diff is collapsed.
......@@ -80,6 +80,8 @@ public:
inline T* begin() const { return ptr; }
inline T* end() const { return ptr + size_; }
inline T& front() const { return *ptr; }
inline T& back() const { return *(ptr + size_ - 1); }
inline ArrayPtr slice(size_t start, size_t end) {
CAPNPROTO_DEBUG_ASSERT(start <= end && end <= size_, "Out-of-bounds ArrayPtr::slice().");
......@@ -137,6 +139,8 @@ public:
inline T* begin() const { return ptr; }
inline T* end() const { return ptr + size_; }
inline T& front() const { return *ptr; }
inline T& back() const { return *(ptr + size_ - 1); }
inline ArrayPtr<T> slice(size_t start, size_t end) {
CAPNPROTO_DEBUG_ASSERT(start <= end && end <= size_, "Out-of-bounds Array::slice().");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment