Skip to content

Commit

Permalink
Removed most heap allocations in builder (#6662)
Browse files Browse the repository at this point in the history
* [C++] Removed most heap allocations in builder

* Updated API docs to indicate heap usage

* Override assertion for heap allocation in parser

* Cleaned up implemenations, enable heap alloc for tests

* Generalized CreateVectorOfStrings

* remove cmake option for heap alloc. reverted two heap removals

* Only use scratch space for vector of strings

* Override Windows SCL warning

* Changed std::transform to for loop to avoid MSCV warnings

* switched to const iterators

* Replaced iterator with for loop

* remove std::to_string in test to be compatible
  • Loading branch information
dbaileychess authored May 21, 2021
1 parent b4e67f9 commit d84bccb
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 19 deletions.
5 changes: 5 additions & 0 deletions include/flatbuffers/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ namespace flatbuffers {
#endif // __has_include
#endif // !FLATBUFFERS_HAS_STRING_VIEW

#ifndef FLATBUFFERS_GENERAL_HEAP_ALLOC_OK
// Allow heap allocations to be used
#define FLATBUFFERS_GENERAL_HEAP_ALLOC_OK 1
#endif // !FLATBUFFERS_GENERAL_HEAP_ALLOC_OK

#ifndef FLATBUFFERS_HAS_NEW_STRTOD
// Modern (C++11) strtod and strtof functions are available for use.
// 1) nan/inf strings as argument of strtod;
Expand Down
73 changes: 54 additions & 19 deletions include/flatbuffers/flatbuffers.h
Original file line number Diff line number Diff line change
Expand Up @@ -1356,9 +1356,8 @@ class FlatBufferBuilder {
// Write a single aligned scalar to the buffer
template<typename T> uoffset_t PushElement(T element) {
AssertScalarT<T>();
T litle_endian_element = EndianScalar(element);
Align(sizeof(T));
buf_.push_small(litle_endian_element);
buf_.push_small(EndianScalar(element));
return GetSize();
}

Expand Down Expand Up @@ -1601,11 +1600,13 @@ class FlatBufferBuilder {

/// @brief Store a string in the buffer, which can contain any binary data.
/// If a string with this exact contents has already been serialized before,
/// instead simply returns the offset of the existing string.
/// instead simply returns the offset of the existing string. This uses a map
/// stored on the heap, but only stores the numerical offsets.
/// @param[in] str A const char pointer to the data to be stored as a string.
/// @param[in] len The number of bytes that should be stored from `str`.
/// @return Returns the offset in the buffer where the string starts.
Offset<String> CreateSharedString(const char *str, size_t len) {
FLATBUFFERS_ASSERT(FLATBUFFERS_GENERAL_HEAP_ALLOC_OK);
if (!string_pool)
string_pool = new StringOffsetMap(StringOffsetCompare(buf_));
auto size_before_string = buf_.size();
Expand All @@ -1627,7 +1628,8 @@ class FlatBufferBuilder {
#ifdef FLATBUFFERS_HAS_STRING_VIEW
/// @brief Store a string in the buffer, which can contain any binary data.
/// If a string with this exact contents has already been serialized before,
/// instead simply returns the offset of the existing string.
/// instead simply returns the offset of the existing string. This uses a map
/// stored on the heap, but only stores the numerical offsets.
/// @param[in] str A const std::string_view to store in the buffer.
/// @return Returns the offset in the buffer where the string starts
Offset<String> CreateSharedString(const flatbuffers::string_view str) {
Expand All @@ -1636,7 +1638,8 @@ class FlatBufferBuilder {
#else
/// @brief Store a string in the buffer, which null-terminated.
/// If a string with this exact contents has already been serialized before,
/// instead simply returns the offset of the existing string.
/// instead simply returns the offset of the existing string. This uses a map
/// stored on the heap, but only stores the numerical offsets.
/// @param[in] str A const char pointer to a C-string to add to the buffer.
/// @return Returns the offset in the buffer where the string starts.
Offset<String> CreateSharedString(const char *str) {
Expand All @@ -1645,7 +1648,8 @@ class FlatBufferBuilder {

/// @brief Store a string in the buffer, which can contain any binary data.
/// If a string with this exact contents has already been serialized before,
/// instead simply returns the offset of the existing string.
/// instead simply returns the offset of the existing string. This uses a map
/// stored on the heap, but only stores the numerical offsets.
/// @param[in] str A const reference to a std::string to store in the buffer.
/// @return Returns the offset in the buffer where the string starts.
Offset<String> CreateSharedString(const std::string &str) {
Expand All @@ -1655,7 +1659,8 @@ class FlatBufferBuilder {

/// @brief Store a string in the buffer, which can contain any binary data.
/// If a string with this exact contents has already been serialized before,
/// instead simply returns the offset of the existing string.
/// instead simply returns the offset of the existing string. This uses a map
/// stored on the heap, but only stores the numerical offsets.
/// @param[in] str A const pointer to a `String` struct to add to the buffer.
/// @return Returns the offset in the buffer where the string starts
Offset<String> CreateSharedString(const String *str) {
Expand Down Expand Up @@ -1762,15 +1767,18 @@ class FlatBufferBuilder {
/// where the vector is stored.
template<typename T> Offset<Vector<T>> CreateVector(size_t vector_size,
const std::function<T (size_t i)> &f) {
FLATBUFFERS_ASSERT(FLATBUFFERS_GENERAL_HEAP_ALLOC_OK);
std::vector<T> elems(vector_size);
for (size_t i = 0; i < vector_size; i++) elems[i] = f(i);
return CreateVector(elems);
}
#endif
#endif // FLATBUFFERS_CPP98_STL
// clang-format on

/// @brief Serialize values returned by a function into a FlatBuffer `vector`.
/// This is a convenience function that takes care of iteration for you.
/// This is a convenience function that takes care of iteration for you. This
/// uses a vector stored on the heap to store the intermediate results of the
/// iteration.
/// @tparam T The data type of the `std::vector` elements.
/// @param f A function that takes the current iteration 0..vector_size-1,
/// and the state parameter returning any type that you can construct a
Expand All @@ -1780,6 +1788,7 @@ class FlatBufferBuilder {
/// where the vector is stored.
template<typename T, typename F, typename S>
Offset<Vector<T>> CreateVector(size_t vector_size, F f, S *state) {
FLATBUFFERS_ASSERT(FLATBUFFERS_GENERAL_HEAP_ALLOC_OK);
std::vector<T> elems(vector_size);
for (size_t i = 0; i < vector_size; i++) elems[i] = f(i, state);
return CreateVector(elems);
Expand All @@ -1793,9 +1802,35 @@ class FlatBufferBuilder {
/// where the vector is stored.
Offset<Vector<Offset<String>>> CreateVectorOfStrings(
const std::vector<std::string> &v) {
std::vector<Offset<String>> offsets(v.size());
for (size_t i = 0; i < v.size(); i++) offsets[i] = CreateString(v[i]);
return CreateVector(offsets);
return CreateVectorOfStrings(v.cbegin(), v.cend());
}

/// @brief Serialize a collection of Strings into a FlatBuffer `vector`.
/// This is a convenience function for a common case.
/// @param begin The begining iterator of the collection
/// @param end The ending iterator of the collection
/// @return Returns a typed `Offset` into the serialized data indicating
/// where the vector is stored.
template<class It>
Offset<Vector<Offset<String>>> CreateVectorOfStrings(It begin, It end) {
auto size = std::distance(begin, end);
auto scratch_buffer_usage = size * sizeof(Offset<String>);
// If there is not enough space to store the offsets, there definitely won't
// be enough space to store all the strings. So ensuring space for the
// scratch region is OK, for it it fails, it would have failed later.
buf_.ensure_space(scratch_buffer_usage);
for (auto it = begin; it != end; ++it) {
buf_.scratch_push_small(CreateString(*it));
}
StartVector(size, sizeof(Offset<String>));
for (auto i = 1; i <= size; i++) {
// Note we re-evaluate the buf location each iteration to account for any
// underlying buffer resizing that may occur.
PushElement(*reinterpret_cast<Offset<String> *>(
buf_.scratch_end() - i * sizeof(Offset<String>)));
}
buf_.scratch_pop(scratch_buffer_usage);
return Offset<Vector<Offset<String>>>(EndVector(size));
}

/// @brief Serialize an array of structs into a FlatBuffer `vector`.
Expand Down Expand Up @@ -1826,9 +1861,9 @@ class FlatBufferBuilder {
Offset<Vector<const T *>> CreateVectorOfNativeStructs(
const S *v, size_t len, T((*const pack_func)(const S &))) {
FLATBUFFERS_ASSERT(pack_func);
std::vector<T> vv(len);
std::transform(v, v + len, vv.begin(), pack_func);
return CreateVectorOfStructs<T>(data(vv), vv.size());
auto structs = StartVectorOfStructs<T>(len);
for (size_t i = 0; i < len; i++) { structs[i] = pack_func(v[i]); }
return EndVectorOfStructs<T>(len);
}

/// @brief Serialize an array of native structs into a FlatBuffer `vector`.
Expand Down Expand Up @@ -1994,10 +2029,10 @@ class FlatBufferBuilder {
Offset<Vector<const T *>> CreateVectorOfSortedNativeStructs(S *v,
size_t len) {
extern T Pack(const S &);
typedef T (*Pack_t)(const S &);
std::vector<T> vv(len);
std::transform(v, v + len, vv.begin(), static_cast<Pack_t &>(Pack));
return CreateVectorOfSortedStructs<T>(vv, len);
auto structs = StartVectorOfStructs<T>(len);
for (size_t i = 0; i < len; i++) { structs[i] = Pack(v[i]); }
std::sort(structs, structs + len, StructKeyComparator<T>());
return EndVectorOfStructs<T>(len);
}

/// @cond FLATBUFFERS_INTERNAL
Expand Down
10 changes: 10 additions & 0 deletions tests/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/
#include <cmath>
#include <string>

#include "flatbuffers/flatbuffers.h"
#include "flatbuffers/idl.h"
Expand Down Expand Up @@ -156,6 +157,15 @@ flatbuffers::DetachedBuffer CreateFlatBufferTest(std::string &buffer) {
names2.push_back("mary");
auto vecofstrings2 = builder.CreateVectorOfStrings(names2);

// Create many vectors of strings
std::vector<std::string> manyNames;
for (auto i = 0; i < 100; i++) { manyNames.push_back("john_doe"); }
auto manyNamesVec = builder.CreateVectorOfStrings(manyNames);
TEST_EQ(false, manyNamesVec.IsNull());
auto manyNamesVec2 =
builder.CreateVectorOfStrings(manyNames.cbegin(), manyNames.cend());
TEST_EQ(false, manyNamesVec2.IsNull());

// Create an array of sorted tables, can be used with binary search when read:
auto vecoftables = builder.CreateVectorOfSortedTables(mlocs, 3);

Expand Down

0 comments on commit d84bccb

Please sign in to comment.