Improve flatbuffer reflection performance
Change-Id: I86b4ce21e8fe4ac2913dcd2c9e3c466c3f66a227
diff --git a/aos/BUILD b/aos/BUILD
index d05a086..294b218 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -381,6 +381,7 @@
hdrs = ["json_to_flatbuffer.h"],
visibility = ["//visibility:public"],
deps = [
+ ":fast_string_builder",
":flatbuffer_utils",
":flatbuffers",
":json_tokenizer",
@@ -522,3 +523,18 @@
"@com_github_google_glog//:glog",
],
)
+
+cc_library(
+ name = "fast_string_builder",
+ srcs = [
+ "fast_string_builder.cc",
+ ],
+ hdrs = [
+ "fast_string_builder.h",
+ ],
+ deps = [
+ "@com_github_google_glog//:glog",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/strings:str_format",
+ ],
+)
diff --git a/aos/aos_dump.cc b/aos/aos_dump.cc
index 55ca57e..fe6cbd5 100644
--- a/aos/aos_dump.cc
+++ b/aos/aos_dump.cc
@@ -15,29 +15,26 @@
namespace {
-void PrintMessage(const aos::Channel *channel, const aos::Context &context) {
+void PrintMessage(const aos::Channel *channel, const aos::Context &context,
+ aos::FastStringBuilder *builder) {
// Print the flatbuffer out to stdout, both to remove the
// unnecessary cruft from glog and to allow the user to readily
// redirect just the logged output independent of any debugging
// information on stderr.
+
+ builder->Reset();
+ aos::FlatbufferToJson(builder, channel->schema(),
+ static_cast<const uint8_t *>(context.data),
+ {false, static_cast<size_t>(FLAGS_max_vector_size)});
+
if (context.monotonic_remote_time != context.monotonic_event_time) {
std::cout << context.realtime_remote_time << " ("
<< context.monotonic_remote_time << ") delivered "
<< context.realtime_event_time << " ("
- << context.monotonic_event_time << "): "
- << aos::FlatbufferToJson(
- channel->schema(),
- static_cast<const uint8_t *>(context.data),
- {false, static_cast<size_t>(FLAGS_max_vector_size)})
- << '\n';
+ << context.monotonic_event_time << "): " << *builder << '\n';
} else {
std::cout << context.realtime_event_time << " ("
- << context.monotonic_event_time << "): "
- << aos::FlatbufferToJson(
- channel->schema(),
- static_cast<const uint8_t *>(context.data),
- {false, static_cast<size_t>(FLAGS_max_vector_size)})
- << '\n';
+ << context.monotonic_event_time << "): " << *builder << '\n';
}
}
@@ -99,19 +96,22 @@
LOG(FATAL) << "Multiple channels found with same type";
}
+ aos::FastStringBuilder str_builder;
+
for (const aos::Channel *channel : found_channels) {
if (FLAGS_fetch) {
const std::unique_ptr<aos::RawFetcher> fetcher =
event_loop.MakeRawFetcher(channel);
if (fetcher->Fetch()) {
- PrintMessage(channel, fetcher->context());
+ PrintMessage(channel, fetcher->context(), &str_builder);
}
}
- event_loop.MakeRawWatcher(channel, [channel](const aos::Context &context,
- const void * /*message*/) {
- PrintMessage(channel, context);
- });
+ event_loop.MakeRawWatcher(
+ channel, [channel, &str_builder](const aos::Context &context,
+ const void * /*message*/) {
+ PrintMessage(channel, context, &str_builder);
+ });
}
event_loop.Run();
diff --git a/aos/fast_string_builder.cc b/aos/fast_string_builder.cc
new file mode 100644
index 0000000..97d2fea
--- /dev/null
+++ b/aos/fast_string_builder.cc
@@ -0,0 +1,67 @@
+#include "fast_string_builder.h"
+
+namespace aos {
+
+FastStringBuilder::FastStringBuilder(std::size_t initial_size) {
+ str_.reserve(initial_size);
+}
+
+void FastStringBuilder::Reset() { str_.resize(0); }
+
+std::string_view FastStringBuilder::Result() const {
+ return std::string_view(str_);
+}
+
+std::string FastStringBuilder::MoveResult() {
+ std::string result;
+ result.swap(str_);
+ return result;
+}
+
+void FastStringBuilder::Resize(std::size_t chars_needed) {
+ str_.resize(str_.size() + chars_needed);
+}
+
+void FastStringBuilder::Append(std::string_view str) {
+ std::size_t index = str_.size();
+ Resize(str.size());
+ str_.replace(index, str.size(), str, 0);
+}
+
+void FastStringBuilder::Append(float val) {
+ std::size_t index = str_.size();
+ Resize(17);
+ int result = absl::SNPrintF(str_.data() + index, 17, "%.6g", val);
+ PCHECK(result != -1);
+ CHECK(result < 17);
+ str_.resize(index + result);
+}
+
+void FastStringBuilder::Append(double val) {
+ std::size_t index = str_.size();
+ Resize(25);
+ int result = absl::SNPrintF(str_.data() + index, 25, "%.15g", val);
+ PCHECK(result != -1);
+ CHECK(result < 25);
+ str_.resize(index + result);
+}
+
+void FastStringBuilder::AppendChar(char val) {
+ Resize(1);
+ str_[str_.size() - 1] = val;
+}
+
+void FastStringBuilder::AppendBool(bool val) {
+ if (bool_to_str_) {
+ Append(val ? kTrue : kFalse);
+ } else {
+ AppendChar(val ? '1' : '0');
+ }
+}
+
+std::ostream &operator<<(std::ostream &os, const FastStringBuilder &builder) {
+ os.write(builder.str_.data(), builder.str_.size());
+ return os;
+}
+
+} // namespace aos
diff --git a/aos/fast_string_builder.h b/aos/fast_string_builder.h
new file mode 100644
index 0000000..9e111ff
--- /dev/null
+++ b/aos/fast_string_builder.h
@@ -0,0 +1,82 @@
+#ifndef AOS_FAST_STRING_BUILDER_H_
+#define AOS_FAST_STRING_BUILDER_H_
+
+#include <ostream>
+#include <string>
+#include <type_traits>
+
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_format.h"
+#include "glog/logging.h"
+
+namespace aos {
+
+// Simplified string builder which is faster than standard classes
+// (std::string::append(), std::ostringstream). Reduces copies on insertion
+// and retrieval, uses fast number to string conversions, and allows reuse
+// of the internal buffer.
+class FastStringBuilder {
+ public:
+ FastStringBuilder(std::size_t initial_size = 64);
+
+ // Convert bools from integer representation to "true" and "false".
+ // Defaults on.
+ void set_bool_to_str(bool bool_to_str) { bool_to_str_ = bool_to_str; }
+
+ // Clears result, allows for reuse of builder without reallocation.
+ // Invalidates Result()
+ void Reset();
+
+ // Returns a temporary view to the string result. Invalidated on destruction
+ // of builder, after calling Reset(), or after calling MoveResult().
+ std::string_view Result() const;
+
+ // Release the string held by the builder. Resets builder and invalidates
+ // Result().
+ std::string MoveResult();
+
+ // Append integer to result, converted to string representation.
+ template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+ void AppendInt(T val);
+
+ void Append(std::string_view);
+
+ void Append(const char *c) { Append(std::string_view(c)); }
+
+ // Append character to result as character.
+ void AppendChar(char);
+
+ // Append float or double to result, converted to string representation
+ void Append(float);
+
+ void Append(double);
+
+ // Append bool to result. set_bool_to_str() toggles between integer and string
+ // representation.
+ void AppendBool(bool);
+
+ // Prints the current result to output
+ friend std::ostream &operator<<(std::ostream &os,
+ const FastStringBuilder &builder);
+
+ private:
+ static const inline std::string kTrue = "true", kFalse = "false";
+
+ // Allocate additional space of at least chars_needed relative to index_.
+ void Resize(std::size_t chars_needed);
+
+ std::string str_;
+ bool bool_to_str_ = true;
+};
+
+template <typename T, typename>
+void FastStringBuilder::AppendInt(T val) {
+ std::size_t index = str_.size();
+ Resize(absl::numbers_internal::kFastToBufferSize);
+ char *end = absl::numbers_internal::FastIntToBuffer(val, str_.data() + index);
+ str_.resize(end - str_.data());
+}
+
+} // namespace aos
+
+#endif // AOS_FAST_STRING_BUIDLER_H_
diff --git a/aos/flatbuffer_introspection.cc b/aos/flatbuffer_introspection.cc
index 48200b7..0fc37a7 100644
--- a/aos/flatbuffer_introspection.cc
+++ b/aos/flatbuffer_introspection.cc
@@ -11,57 +11,55 @@
using reflection::BaseType;
void IntToString(int64_t val, reflection::BaseType type,
- std::stringstream *out) {
+ FastStringBuilder *out) {
switch (type) {
case BaseType::Bool:
- *out << (val ? "true" : "false");
+ out->AppendBool(static_cast<bool>(val));
break;
case BaseType::UByte:
- *out << std::to_string(static_cast<uint8_t>(val));
+ out->AppendInt(static_cast<uint8_t>(val));
break;
case BaseType::Byte:
- *out << std::to_string(static_cast<int8_t>(val));
+ out->AppendInt(static_cast<int8_t>(val));
break;
case BaseType::Short:
- *out << static_cast<int16_t>(val);
+ out->AppendInt(static_cast<int16_t>(val));
break;
case BaseType::UShort:
- *out << static_cast<uint16_t>(val);
+ out->AppendInt(static_cast<uint16_t>(val));
break;
case BaseType::Int:
- *out << static_cast<int32_t>(val);
+ out->AppendInt(static_cast<int32_t>(val));
break;
case BaseType::UInt:
- *out << static_cast<uint32_t>(val);
+ out->AppendInt(static_cast<uint32_t>(val));
break;
case BaseType::Long:
- *out << static_cast<int64_t>(val);
+ out->AppendInt(static_cast<int64_t>(val));
break;
case BaseType::ULong:
- *out << static_cast<uint64_t>(val);
+ out->AppendInt(static_cast<uint64_t>(val));
break;
default:
- *out << "null";
+ out->Append("null");
}
}
void FloatToString(double val, reflection::BaseType type,
- std::stringstream *out) {
+ FastStringBuilder *out) {
if (std::isnan(val)) {
- *out << "null";
+ out->Append("null");
return;
}
switch (type) {
case BaseType::Float:
- out->precision(std::numeric_limits<float>::digits10);
- *out << static_cast<float>(val);
+ out->Append(static_cast<float>(val));
break;
case BaseType::Double:
- out->precision(std::numeric_limits<double>::digits10);
- *out << val;
+ out->Append(val);
break;
default:
- *out << "null";
+ out->Append("null");
}
}
@@ -70,39 +68,41 @@
const reflection::Object *obj,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Object>> *objects,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
- const ObjT *object, std::stringstream *out, JsonOptions json_options,
+ const ObjT *object, FastStringBuilder *out, JsonOptions json_options,
int tree_depth = 0);
// Get enum value name
-const char *EnumToString(
+std::string_view EnumToString(
int64_t enum_value,
const flatbuffers::Vector<flatbuffers::Offset<reflection::EnumVal>>
*values) {
- // Replace with binary search? Enum values are pre-sorted.
- for (auto iter = values->begin(); iter != values->end(); iter++) {
- if (enum_value == iter->value()) {
- return iter->name()->c_str();
- }
- }
- return nullptr;
+ auto result = std::find_if(values->begin(), values->end(),
+ [enum_value](const reflection::EnumVal *a) {
+ return a->value() == enum_value;
+ });
+ return result != values->end() ? result->name()->string_view()
+ : std::string_view();
}
// Convert integer to string, checking if it is an enum.
void IntOrEnumToString(
int64_t val, const reflection::Type *type,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
- std::stringstream *out) {
+ FastStringBuilder *out) {
// Check if integer is an enum and print string, otherwise fallback to
// printing as int.
if (type->index() > -1 && type->index() < (int32_t)enums->size()) {
const reflection::Enum *enum_props = enums->Get(type->index());
if (!enum_props->is_union()) {
- const char *value_string = EnumToString(val, enum_props->values());
+ const std::string_view value_string =
+ EnumToString(val, enum_props->values());
- if (value_string != nullptr) {
- *out << '"' << value_string << '"';
+ if (value_string.data() != nullptr) {
+ out->AppendChar('"');
+ out->Append(value_string);
+ out->AppendChar('"');
} else {
- *out << val;
+ out->AppendInt(val);
}
}
} else {
@@ -117,10 +117,10 @@
// Adds a newline and indents
// Every increment in tree depth is two spaces
-void AddWrapping(std::stringstream *out, int tree_depth) {
- *out << "\n";
+void AddWrapping(FastStringBuilder *out, int tree_depth) {
+ out->Append("\n");
for (int i = 0; i < tree_depth; i++) {
- *out << " ";
+ out->Append(" ");
}
}
@@ -141,7 +141,7 @@
const ObjT *table, const reflection::Field *field,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Object>> *objects,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
- std::stringstream *out, JsonOptions json_options, int tree_depth) {
+ FastStringBuilder *out, JsonOptions json_options, int tree_depth) {
const reflection::Type *type = field->type();
switch (type->base_type()) {
@@ -162,40 +162,39 @@
break;
case BaseType::String:
if constexpr (std::is_same<flatbuffers::Table, ObjT>()) {
- std::string str = flatbuffers::GetFieldS(*table, *field)->str();
- std::string out_str;
- out_str.reserve(str.size());
+ flatbuffers::string_view str =
+ flatbuffers::GetFieldS(*table, *field)->string_view();
+ out->AppendChar('"');
for (char c : str) {
- // out_str += c;
switch (c) {
case '"':
- out_str += "\\\"";
+ out->Append("\\\"");
break;
case '\\':
- out_str += "\\\\";
+ out->Append("\\\\");
break;
case '\b':
- out_str += "\\b";
+ out->Append("\\b");
break;
case '\f':
- out_str += "\\f";
+ out->Append("\\f");
break;
case '\n':
- out_str += "\\n";
+ out->Append("\\n");
break;
case '\r':
- out_str += "\\r";
+ out->Append("\\r");
break;
case '\t':
- out_str += "\\t";
+ out->Append("\\t");
break;
default:
- out_str += c;
+ out->AppendChar(c);
}
}
- *out << '"' << out_str << '"';
+ out->AppendChar('"');
} else {
- *out << "null";
+ out->Append("null");
}
break;
case BaseType::Vector: {
@@ -205,7 +204,9 @@
reflection::BaseType elem_type = type->element();
if (vector->size() > json_options.max_vector_size) {
- *out << "[ ... " << vector->size() << " elements ... ]";
+ out->Append("[ ... ");
+ out->AppendInt(vector->size());
+ out->Append(" elements ... ]");
break;
}
@@ -216,13 +217,13 @@
wrap = ShouldCauseWrapping(elem_type);
}
- *out << '[';
+ out->AppendChar('[');
for (flatbuffers::uoffset_t i = 0; i < vector->size(); ++i) {
if (i != 0) {
if (wrap) {
- *out << ",";
+ out->Append(",");
} else {
- *out << ", ";
+ out->Append(", ");
}
}
if (wrap) {
@@ -236,8 +237,9 @@
FloatToString(flatbuffers::GetAnyVectorElemF(vector, elem_type, i),
elem_type, out);
} else if (elem_type == BaseType::String) {
- *out << '"' << flatbuffers::GetAnyVectorElemS(vector, elem_type, i)
- << '"';
+ out->AppendChar('"');
+ out->Append(flatbuffers::GetAnyVectorElemS(vector, elem_type, i));
+ out->AppendChar('"');
} else if (elem_type == BaseType::Obj) {
if (type->index() > -1 &&
type->index() < (int32_t)objects->size()) {
@@ -260,9 +262,9 @@
if (wrap) {
AddWrapping(out, tree_depth);
}
- *out << ']';
+ out->AppendChar(']');
} else {
- *out << "null";
+ out->Append("null");
}
} break;
case BaseType::Obj: {
@@ -277,11 +279,11 @@
json_options, tree_depth);
}
} else {
- *out << "null";
+ out->Append("null");
}
} break;
default:
- *out << "null";
+ out->Append("null");
}
}
@@ -292,7 +294,7 @@
const reflection::Object *obj,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Object>> *objects,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
- const ObjT *object, std::stringstream *out, JsonOptions json_options,
+ const ObjT *object, FastStringBuilder *out, JsonOptions json_options,
int tree_depth) {
static_assert(std::is_same<flatbuffers::Table, ObjT>() ||
std::is_same<flatbuffers::Struct, ObjT>(),
@@ -312,16 +314,16 @@
}
}
- *out << '{';
+ out->AppendChar('{');
for (const reflection::Field *field : *obj->fields()) {
// Check whether this object has the field populated (even for structs,
// which should have all fields populated)
if (object->GetAddressOf(field->offset())) {
if (print_sep) {
if (wrap) {
- *out << ",";
+ out->Append(",");
} else {
- *out << ", ";
+ out->Append(", ");
}
} else {
print_sep = true;
@@ -331,7 +333,9 @@
AddWrapping(out, child_tree_depth);
}
- *out << '"' << field->name()->c_str() << "\": ";
+ out->AppendChar('"');
+ out->Append(field->name()->string_view());
+ out->Append("\": ");
FieldToString(object, field, objects, enums, out, json_options,
child_tree_depth);
}
@@ -341,30 +345,37 @@
AddWrapping(out, tree_depth);
}
- *out << '}';
+ out->AppendChar('}');
}
} // namespace
std::string FlatbufferToJson(const reflection::Schema *schema,
const uint8_t *data, JsonOptions json_options) {
+ FastStringBuilder builder;
+ FlatbufferToJson(&builder, schema, data, json_options);
+ return builder.MoveResult();
+}
+
+void FlatbufferToJson(FastStringBuilder *builder,
+ const reflection::Schema *schema, const uint8_t *data,
+ JsonOptions json_options) {
CHECK(schema != nullptr) << ": Need to provide a schema";
+ CHECK(builder != nullptr) << ": Need to provide an output builder";
// It is pretty common to get passed in a nullptr when a test fails. Rather
// than CHECK, return a more user friendly result.
if (data == nullptr) {
- return "null";
+ builder->Append("null");
+ return;
}
const flatbuffers::Table *table = flatbuffers::GetAnyRoot(data);
const reflection::Object *obj = schema->root_table();
- std::stringstream out;
-
- ObjectToString(obj, schema->objects(), schema->enums(), table, &out,
+ ObjectToString(obj, schema->objects(), schema->enums(), table, builder,
json_options);
-
- return out.str();
}
+
} // namespace aos
diff --git a/aos/json_to_flatbuffer.h b/aos/json_to_flatbuffer.h
index aebd069..9ba50f8 100644
--- a/aos/json_to_flatbuffer.h
+++ b/aos/json_to_flatbuffer.h
@@ -6,6 +6,7 @@
#include <string>
#include <string_view>
+#include "aos/fast_string_builder.h"
#include "aos/flatbuffers.h"
#include "flatbuffers/flatbuffers.h"
#include "flatbuffers/reflection.h"
@@ -82,6 +83,10 @@
const uint8_t *const data,
JsonOptions json_options = {});
+void FlatbufferToJson(FastStringBuilder *builder,
+ const reflection::Schema *const schema,
+ const uint8_t *const data, JsonOptions json_options = {});
+
// Writes a Flatbuffer to a file, or dies.
template <typename T>
inline void WriteFlatbufferToJson(const std::string_view filename,