Commit fb87c0d3 authored by Lawrence Chan's avatar Lawrence Chan Committed by Wouter van Oortmerssen

[C++] Improve Allocator handling (#4312)

* Improve Allocator handling

- Templatize Allocator on vector_downward, and make it own the allocator
instance so it can manage lifetimes.
- Templatize + rename FlatBufferBuilderT accordingly, and add a typedef
to FlatBufferBuilder so old code continues to work.
- Fix some issues with the release deleter
- More details in github issue #4311

* Fix constexpr for older MSVC

* Reimplement allocator improvements via inheritance

Instead of templates, use an abstract base class and some unique_ptrs to
implement the new and improved allocator.

* Fix misplaced newline

* Add missing override keyword

* Add macro for func delete to support older compilers

* Explicitly move BufferDeleter (maybe fixes VS 10?)

* Revert previous attempt at VS10 fix

* Try yet another workaround for MS 10

* Use FLATBUFFERS_NOEXCEPT macro

* Add FLATBUFFERS_OVERRIDE macro

* Fix some issues with MSVC 16 hack

* Remove dep on unique_ptr

* Update DEPRECATED macro with a message

* Sigh, no delegating ctors before c++11

* Fix simple_allocator stub

* Relax FlatBufferBuilder ctor

* Add vector_downward reset + assert in clear

* Rename DetachedBuffer to FlatBuffer + remove unnecessary deprecations

* Add flatbuffers::FlatBufferBuilder::Release()

* Address remaining allocator-improvements comments
parent 398ae0cb
...@@ -105,8 +105,10 @@ ...@@ -105,8 +105,10 @@
#if (!defined(_MSC_VER) || _MSC_VER > 1600) && \ #if (!defined(_MSC_VER) || _MSC_VER > 1600) && \
(!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 407)) (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 407))
#define FLATBUFFERS_FINAL_CLASS final #define FLATBUFFERS_FINAL_CLASS final
#define FLATBUFFERS_OVERRIDE override
#else #else
#define FLATBUFFERS_FINAL_CLASS #define FLATBUFFERS_FINAL_CLASS
#define FLATBUFFERS_OVERRIDE
#endif #endif
#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && \ #if (!defined(_MSC_VER) || _MSC_VER >= 1900) && \
...@@ -123,6 +125,15 @@ ...@@ -123,6 +125,15 @@
#define FLATBUFFERS_NOEXCEPT #define FLATBUFFERS_NOEXCEPT
#endif #endif
// NOTE: the FLATBUFFERS_DELETE_FUNC macro may change the access mode to
// private, so be sure to put it at the end or reset access mode explicitly.
#if (!defined(_MSC_VER) || _MSC_FULL_VER >= 180020827) && \
(!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 404))
#define FLATBUFFERS_DELETE_FUNC(func) func = delete;
#else
#define FLATBUFFERS_DELETE_FUNC(func) private: func;
#endif
#if defined(_MSC_VER) #if defined(_MSC_VER)
#pragma warning(push) #pragma warning(push)
#pragma warning(disable: 4127) // C4127: conditional expression is constant #pragma warning(disable: 4127) // C4127: conditional expression is constant
...@@ -154,12 +165,6 @@ typedef uintmax_t largest_scalar_t; ...@@ -154,12 +165,6 @@ typedef uintmax_t largest_scalar_t;
// We support aligning the contents of buffers up to this size. // We support aligning the contents of buffers up to this size.
#define FLATBUFFERS_MAX_ALIGNMENT 16 #define FLATBUFFERS_MAX_ALIGNMENT 16
#ifndef FLATBUFFERS_CPP98_STL
// Pointer to relinquished memory.
typedef std::unique_ptr<uint8_t, std::function<void(uint8_t * /* unused */)>>
unique_ptr_t;
#endif
// Wrapper for uoffset_t to allow safe template specialization. // Wrapper for uoffset_t to allow safe template specialization.
// Value is allowed to be 0 to indicate a null object (see e.g. AddOffset). // Value is allowed to be 0 to indicate a null object (see e.g. AddOffset).
template<typename T> struct Offset { template<typename T> struct Offset {
...@@ -542,13 +547,128 @@ struct String : public Vector<char> { ...@@ -542,13 +547,128 @@ struct String : public Vector<char> {
} }
}; };
// Simple indirection for buffer allocation, to allow this to be overridden // Allocator interface. This is flatbuffers-specific and meant only for
// with custom allocation (see the FlatBufferBuilder constructor). // `vector_downward` usage.
class simple_allocator { class Allocator {
public: public:
virtual ~simple_allocator() {} virtual ~Allocator() {}
virtual uint8_t *allocate(size_t size) const { return new uint8_t[size]; }
virtual void deallocate(uint8_t *p) const { delete[] p; } // Allocate `size` bytes of memory.
virtual uint8_t *allocate(size_t size) = 0;
// Deallocate `size` bytes of memory at `p` allocated by this allocator.
virtual void deallocate(uint8_t *p, size_t size) = 0;
// Reallocate `new_size` bytes of memory, replacing the old region of size
// `old_size` at `p`. In contrast to a normal realloc, this grows downwards,
// and is intended specifcally for `vector_downward` use.
virtual uint8_t *reallocate_downward(uint8_t *old_p, size_t old_size,
size_t new_size) {
assert(new_size > old_size); // vector_downward only grows
uint8_t *new_p = allocate(new_size);
memcpy(new_p + (new_size - old_size), old_p, old_size);
deallocate(old_p, old_size);
return new_p;
}
};
// DefaultAllocator uses new/delete to allocate memory regions
class DefaultAllocator : public Allocator {
public:
virtual uint8_t *allocate(size_t size) FLATBUFFERS_OVERRIDE {
return new uint8_t[size];
}
virtual void deallocate(uint8_t *p, size_t) FLATBUFFERS_OVERRIDE {
delete[] p;
}
static DefaultAllocator &instance() {
static DefaultAllocator inst;
return inst;
}
};
// DetachedBuffer is a finished flatbuffer memory region, detached from its
// builder. The original memory region and allocator are also stored so that
// the DetachedBuffer can manage the memory lifetime.
class DetachedBuffer {
public:
DetachedBuffer(Allocator *allocator, bool own_allocator, uint8_t *buf,
size_t reserved, uint8_t *cur, size_t sz)
: allocator_(allocator), own_allocator_(own_allocator), buf_(buf),
reserved_(reserved), cur_(cur), size_(sz) {
assert(allocator_);
}
DetachedBuffer(DetachedBuffer &&other)
: allocator_(other.allocator_), own_allocator_(other.own_allocator_),
buf_(other.buf_), reserved_(other.reserved_), cur_(other.cur_),
size_(other.size_) {
assert(allocator_);
other.allocator_ = nullptr;
other.own_allocator_ = false;
other.buf_ = nullptr;
other.reserved_ = 0;
other.cur_ = nullptr;
other.size_ = 0;
}
~DetachedBuffer() {
if (buf_ != nullptr) {
assert(allocator_ != nullptr);
allocator_->deallocate(buf_, reserved_);
}
if (own_allocator_ && allocator_ != nullptr) {
delete allocator_;
}
}
const uint8_t *data() const {
assert(cur_ != nullptr);
return cur_;
}
uint8_t *data() {
assert(cur_ != nullptr);
return cur_;
}
size_t size() const {
assert(cur_ != nullptr);
return size_;
}
#if 0 // disabled for now due to the ordering of classes in this header
template <class T>
bool Verify() const {
Verifier verifier(data(), size());
return verifier.Verify<T>(nullptr);
}
template <class T>
const T* GetRoot() const {
return flatbuffers::GetRoot<T>(data());
}
template <class T>
T* GetRoot() {
return flatbuffers::GetRoot<T>(data());
}
#endif
// These may change access mode, leave these at end of public section
FLATBUFFERS_DELETE_FUNC(DetachedBuffer(const DetachedBuffer &other))
FLATBUFFERS_DELETE_FUNC(
DetachedBuffer &operator=(const DetachedBuffer &other))
protected:
Allocator *allocator_;
bool own_allocator_;
uint8_t *buf_;
size_t reserved_;
uint8_t *cur_;
size_t size_;
}; };
// This is a minimal replication of std::vector<uint8_t> functionality, // This is a minimal replication of std::vector<uint8_t> functionality,
...@@ -556,43 +676,46 @@ class simple_allocator { ...@@ -556,43 +676,46 @@ class simple_allocator {
// in the lowest address in the vector. // in the lowest address in the vector.
class vector_downward { class vector_downward {
public: public:
explicit vector_downward(size_t initial_size, explicit vector_downward(size_t initial_size = 1024,
const simple_allocator &allocator) Allocator *allocator = nullptr,
: reserved_((initial_size + sizeof(largest_scalar_t) - 1) & bool own_allocator = false)
: allocator_(allocator ? allocator : &DefaultAllocator::instance()),
own_allocator_(own_allocator),
reserved_((initial_size + sizeof(largest_scalar_t) - 1) &
~(sizeof(largest_scalar_t) - 1)), ~(sizeof(largest_scalar_t) - 1)),
buf_(allocator.allocate(reserved_)), buf_(allocator_->allocate(reserved_)), cur_(buf_ + reserved_) {
cur_(buf_ + reserved_), assert(allocator_);
allocator_(allocator) {} }
~vector_downward() { ~vector_downward() {
if (buf_) if (buf_ != nullptr) {
allocator_.deallocate(buf_); assert(allocator_ != nullptr);
allocator_->deallocate(buf_, reserved_);
}
if (own_allocator_ && allocator_ != nullptr) {
delete allocator_;
}
} }
void clear() { void clear() {
if (buf_ == nullptr) if (buf_ == nullptr) {
buf_ = allocator_.allocate(reserved_); assert(allocator_ != nullptr);
buf_ = allocator_->allocate(reserved_);
}
cur_ = buf_ + reserved_; cur_ = buf_ + reserved_;
} }
#ifndef FLATBUFFERS_CPP98_STL
// Relinquish the pointer to the caller. // Relinquish the pointer to the caller.
unique_ptr_t release() { DetachedBuffer release() {
// Actually deallocate from the start of the allocated memory. DetachedBuffer fb(allocator_, own_allocator_, buf_, reserved_, cur_,
std::function<void(uint8_t *)> deleter( size());
std::bind(&simple_allocator::deallocate, allocator_, buf_)); allocator_ = nullptr;
own_allocator_ = false;
// Point to the desired offset. reserved_ = 0;
unique_ptr_t retval(data(), deleter);
// Don't deallocate when this instance is destroyed.
buf_ = nullptr; buf_ = nullptr;
cur_ = nullptr; cur_ = nullptr;
return fb;
return retval;
} }
#endif
size_t growth_policy(size_t bytes) { size_t growth_policy(size_t bytes) {
return (bytes / 2) & ~(sizeof(largest_scalar_t) - 1); return (bytes / 2) & ~(sizeof(largest_scalar_t) - 1);
...@@ -649,26 +772,24 @@ class vector_downward { ...@@ -649,26 +772,24 @@ class vector_downward {
private: private:
// You shouldn't really be copying instances of this class. // You shouldn't really be copying instances of this class.
vector_downward(const vector_downward &); FLATBUFFERS_DELETE_FUNC(vector_downward(const vector_downward &))
vector_downward &operator=(const vector_downward &); FLATBUFFERS_DELETE_FUNC(vector_downward &operator=(const vector_downward &))
Allocator *allocator_;
bool own_allocator_;
size_t reserved_; size_t reserved_;
uint8_t *buf_; uint8_t *buf_;
uint8_t *cur_; // Points at location between empty (below) and used (above). uint8_t *cur_; // Points at location between empty (below) and used (above).
const simple_allocator &allocator_;
void reallocate(size_t len) { void reallocate(size_t len) {
size_t old_reserved = reserved_;
auto old_size = size(); auto old_size = size();
auto largest_align = AlignOf<largest_scalar_t>(); auto largest_align = AlignOf<largest_scalar_t>();
reserved_ += (std::max)(len, growth_policy(reserved_)); reserved_ += (std::max)(len, growth_policy(reserved_));
// Round up to avoid undefined behavior from unaligned loads and stores. // Round up to avoid undefined behavior from unaligned loads and stores.
reserved_ = (reserved_ + (largest_align - 1)) & ~(largest_align - 1); reserved_ = (reserved_ + (largest_align - 1)) & ~(largest_align - 1);
auto new_buf = allocator_.allocate(reserved_); buf_ = allocator_->reallocate_downward(buf_, old_reserved, reserved_);
auto new_cur = new_buf + reserved_ - old_size; cur_ = buf_ + reserved_ - old_size;
memcpy(new_cur, cur_, old_size);
cur_ = new_cur;
allocator_.deallocate(buf_);
buf_ = new_buf;
} }
}; };
...@@ -712,14 +833,16 @@ FLATBUFFERS_FINAL_CLASS ...@@ -712,14 +833,16 @@ FLATBUFFERS_FINAL_CLASS
public: public:
/// @brief Default constructor for FlatBufferBuilder. /// @brief Default constructor for FlatBufferBuilder.
/// @param[in] initial_size The initial size of the buffer, in bytes. Defaults /// @param[in] initial_size The initial size of the buffer, in bytes. Defaults
/// to`1024`. /// to `1024`.
/// @param[in] allocator A pointer to the `simple_allocator` that should be /// @param[in] allocator An `Allocator` to use. Defaults to a new instance of
/// used. Defaults to `nullptr`, which means the `default_allocator` will be /// a `DefaultAllocator`.
/// be used. /// @param[in] own_allocator Whether the builder/vector should own the
/// allocator. Defaults to / `false`.
explicit FlatBufferBuilder(uoffset_t initial_size = 1024, explicit FlatBufferBuilder(uoffset_t initial_size = 1024,
const simple_allocator *allocator = nullptr) Allocator *allocator = nullptr,
: buf_(initial_size, allocator ? *allocator : default_allocator), bool own_allocator = false)
nested(false), finished(false), minalign_(1), force_defaults_(false), : buf_(initial_size, allocator, own_allocator), nested(false),
finished(false), minalign_(1), force_defaults_(false),
dedup_vtables_(true), string_pool(nullptr) { dedup_vtables_(true), string_pool(nullptr) {
offsetbuf_.reserve(16); // Avoid first few reallocs. offsetbuf_.reserve(16); // Avoid first few reallocs.
vtables_.reserve(16); vtables_.reserve(16);
...@@ -758,18 +881,22 @@ FLATBUFFERS_FINAL_CLASS ...@@ -758,18 +881,22 @@ FLATBUFFERS_FINAL_CLASS
/// @return Returns a `uint8_t` pointer to the unfinished buffer. /// @return Returns a `uint8_t` pointer to the unfinished buffer.
uint8_t *GetCurrentBufferPointer() const { return buf_.data(); } uint8_t *GetCurrentBufferPointer() const { return buf_.data(); }
#ifndef FLATBUFFERS_CPP98_STL
/// @brief Get the released pointer to the serialized buffer. /// @brief Get the released pointer to the serialized buffer.
/// @warning Do NOT attempt to use this FlatBufferBuilder afterwards! /// @warning Do NOT attempt to use this FlatBufferBuilder afterwards!
/// @return The `unique_ptr` returned has a special allocator that knows how /// @return A `FlatBuffer` that owns the buffer and its allocator and
/// to deallocate this pointer (since it points to the middle of an /// behaves similar to a `unique_ptr` with a deleter.
/// allocation). Thus, do not mix this pointer with other `unique_ptr`'s, or /// Deprecated: use Release() instead
/// call `release()`/`reset()` on it. DetachedBuffer ReleaseBufferPointer() {
unique_ptr_t ReleaseBufferPointer() { Finished();
return buf_.release();
}
/// @brief Get the released DetachedBuffer.
/// @return A `DetachedBuffer` that owns the buffer and its allocator.
DetachedBuffer Release() {
Finished(); Finished();
return buf_.release(); return buf_.release();
} }
#endif
/// @brief get the minimum alignment this buffer needs to be accessed /// @brief get the minimum alignment this buffer needs to be accessed
/// properly. This is only known once all elements have been written (after /// properly. This is only known once all elements have been written (after
...@@ -1465,8 +1592,6 @@ FLATBUFFERS_FINAL_CLASS ...@@ -1465,8 +1592,6 @@ FLATBUFFERS_FINAL_CLASS
voffset_t id; voffset_t id;
}; };
simple_allocator default_allocator;
vector_downward buf_; vector_downward buf_;
// Accumulating offsets of table members while it is being built. // Accumulating offsets of table members while it is being built.
......
...@@ -83,7 +83,7 @@ void lcg_reset() { lcg_seed = 48271; } ...@@ -83,7 +83,7 @@ void lcg_reset() { lcg_seed = 48271; }
std::string test_data_path = "tests/"; std::string test_data_path = "tests/";
// example of how to build up a serialized buffer algorithmically: // example of how to build up a serialized buffer algorithmically:
flatbuffers::unique_ptr_t CreateFlatBufferTest(std::string &buffer) { flatbuffers::DetachedBuffer CreateFlatBufferTest(std::string &buffer) {
flatbuffers::FlatBufferBuilder builder; flatbuffers::FlatBufferBuilder builder;
auto vec = Vec3(1, 2, 3, 0, Color_Red, Test(10, 20)); auto vec = Vec3(1, 2, 3, 0, Color_Red, Test(10, 20));
...@@ -1520,11 +1520,11 @@ int main(int /*argc*/, const char * /*argv*/[]) { ...@@ -1520,11 +1520,11 @@ int main(int /*argc*/, const char * /*argv*/[]) {
auto flatbuf = CreateFlatBufferTest(rawbuf); auto flatbuf = CreateFlatBufferTest(rawbuf);
AccessFlatBufferTest(reinterpret_cast<const uint8_t *>(rawbuf.c_str()), AccessFlatBufferTest(reinterpret_cast<const uint8_t *>(rawbuf.c_str()),
rawbuf.length()); rawbuf.length());
AccessFlatBufferTest(flatbuf.get(), rawbuf.length()); AccessFlatBufferTest(flatbuf.data(), flatbuf.size());
MutateFlatBuffersTest(flatbuf.get(), rawbuf.length()); MutateFlatBuffersTest(flatbuf.data(), flatbuf.size());
ObjectFlatBuffersTest(flatbuf.get()); ObjectFlatBuffersTest(flatbuf.data());
SizePrefixedTest(); SizePrefixedTest();
...@@ -1534,7 +1534,7 @@ int main(int /*argc*/, const char * /*argv*/[]) { ...@@ -1534,7 +1534,7 @@ int main(int /*argc*/, const char * /*argv*/[]) {
test_data_path; test_data_path;
#endif #endif
ParseAndGenerateTextTest(); ParseAndGenerateTextTest();
ReflectionTest(flatbuf.get(), rawbuf.length()); ReflectionTest(flatbuf.data(), flatbuf.size());
ParseProtoTest(); ParseProtoTest();
UnionVectorTest(); UnionVectorTest();
#endif #endif
......
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