From 08943aa26fe9f5ff960e2b102435c7624347446e Mon Sep 17 00:00:00 2001 From: mrmarkwell Date: Tue, 6 Oct 2020 10:56:45 -0700 Subject: [PATCH] Flatbuffer C++ UnpackTo optimization for vectors of non-bool bytes. (#6154) UnpackTo copies vector elements one-by-one, which can be very inefficient depending on the quality of the compiler optimizations performed. This change updates the operation for vectors of bytes that aren't enums to use 'std::copy', which is usually highly optimized. vectors of types that are more than one byte can't be optimized in this way because of the endianness of the serialized bytes vs. the target architecture endianness. vectors of enums can't be optimized because they are required to be static_cast into the appropriate enum type when stored in the vector. vectors of bools can be optimized in most cases, but since the standard allows std::vector template specialization for space-savings, std::copy doesn't work on every implementation (looking at you Microsoft). Thus, this optimization is skipped for vector. For a specific example, this improves the latency of unpacking large buffers on the Hexagon DSP by about 10x. Co-authored-by: Matthew Markwell --- samples/monster_generated.h | 2 +- src/idl_gen_cpp.cpp | 107 ++++++++++-------- .../generated_cpp17/monster_test_generated.h | 8 +- tests/monster_test_generated.h | 8 +- 4 files changed, 71 insertions(+), 54 deletions(-) diff --git a/samples/monster_generated.h b/samples/monster_generated.h index 531d65fdc..8afde1ef8 100644 --- a/samples/monster_generated.h +++ b/samples/monster_generated.h @@ -580,7 +580,7 @@ inline void Monster::UnPackTo(MonsterT *_o, const flatbuffers::resolver_function { auto _e = mana(); _o->mana = _e; } { auto _e = hp(); _o->hp = _e; } { auto _e = name(); if (_e) _o->name = _e->str(); } - { auto _e = inventory(); if (_e) { _o->inventory.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->inventory[_i] = _e->Get(_i); } } } + { auto _e = inventory(); if (_e) { _o->inventory.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->inventory.begin()); } } { auto _e = color(); _o->color = _e; } { auto _e = weapons(); if (_e) { _o->weapons.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->weapons[_i] = flatbuffers::unique_ptr(_e->Get(_i)->UnPack(_resolver)); } } } { auto _e = equipped_type(); _o->equipped.type = _e; } diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp index 404353378..dabc385bb 100644 --- a/src/idl_gen_cpp.cpp +++ b/src/idl_gen_cpp.cpp @@ -2498,58 +2498,75 @@ class CppGenerator : public BaseGenerator { std::string code; switch (field.value.type.base_type) { case BASE_TYPE_VECTOR: { - auto cpp_type = field.attributes.Lookup("cpp_type"); - std::string indexing; - if (field.value.type.enum_def) { - indexing += "static_cast<" + - WrapInNameSpace(*field.value.type.enum_def) + ">("; - } - indexing += "_e->Get(_i)"; - if (field.value.type.enum_def) { indexing += ")"; } - if (field.value.type.element == BASE_TYPE_BOOL) { indexing += " != 0"; } - - // Generate code that pushes data from _e to _o in the form: - // for (uoffset_t i = 0; i < _e->size(); ++i) { - // _o->field.push_back(_e->Get(_i)); - // } auto name = Name(field); if (field.value.type.element == BASE_TYPE_UTYPE) { name = StripUnionType(Name(field)); } - auto access = - field.value.type.element == BASE_TYPE_UTYPE - ? ".type" - : (field.value.type.element == BASE_TYPE_UNION ? ".value" : ""); code += "{ _o->" + name + ".resize(_e->size()); "; - code += "for (flatbuffers::uoffset_t _i = 0;"; - code += " _i < _e->size(); _i++) { "; - if (cpp_type) { - // Generate code that resolves the cpp pointer type, of the form: - // if (resolver) - // (*resolver)(&_o->field, (hash_value_t)(_e)); - // else - // _o->field = nullptr; - code += "//vector resolver, " + PtrType(&field) + "\n"; - code += "if (_resolver) "; - code += "(*_resolver)"; - code += "(reinterpret_cast(&_o->" + name + "[_i]" + access + - "), "; - code += "static_cast(" + indexing + "));"; - if (PtrType(&field) == "naked") { - code += " else "; - code += "_o->" + name + "[_i]" + access + " = nullptr"; - } else { - // code += " else "; - // code += "_o->" + name + "[_i]" + access + " = " + - // GenTypeNativePtr(cpp_type->constant, &field, true) + "();"; - code += "/* else do nothing */"; - } + if (!field.value.type.enum_def && !IsBool(field.value.type.element) && + IsOneByte(field.value.type.element)) { + // For vectors of bytes, std::copy is used to improve performance. + // This doesn't work for: + // - enum types because they have to be explicitly static_cast. + // - vectors of bool, since they are a template specialization. + // - multiple-byte types due to endianness. + code += + "std::copy(_e->begin(), _e->end(), _o->" + name + ".begin()); }"; } else { - code += "_o->" + name + "[_i]" + access + " = "; - code += GenUnpackVal(field.value.type.VectorType(), indexing, true, - field); + std::string indexing; + if (field.value.type.enum_def) { + indexing += "static_cast<" + + WrapInNameSpace(*field.value.type.enum_def) + ">("; + } + indexing += "_e->Get(_i)"; + if (field.value.type.enum_def) { + indexing += ")"; + } + if (field.value.type.element == BASE_TYPE_BOOL) { + indexing += " != 0"; + } + // Generate code that pushes data from _e to _o in the form: + // for (uoffset_t i = 0; i < _e->size(); ++i) { + // _o->field.push_back(_e->Get(_i)); + // } + auto access = + field.value.type.element == BASE_TYPE_UTYPE + ? ".type" + : (field.value.type.element == BASE_TYPE_UNION ? ".value" + : ""); + + code += "for (flatbuffers::uoffset_t _i = 0;"; + code += " _i < _e->size(); _i++) { "; + auto cpp_type = field.attributes.Lookup("cpp_type"); + if (cpp_type) { + // Generate code that resolves the cpp pointer type, of the form: + // if (resolver) + // (*resolver)(&_o->field, (hash_value_t)(_e)); + // else + // _o->field = nullptr; + code += "//vector resolver, " + PtrType(&field) + "\n"; + code += "if (_resolver) "; + code += "(*_resolver)"; + code += "(reinterpret_cast(&_o->" + name + "[_i]" + + access + "), "; + code += + "static_cast(" + indexing + "));"; + if (PtrType(&field) == "naked") { + code += " else "; + code += "_o->" + name + "[_i]" + access + " = nullptr"; + } else { + // code += " else "; + // code += "_o->" + name + "[_i]" + access + " = " + + // GenTypeNativePtr(cpp_type->constant, &field, true) + "();"; + code += "/* else do nothing */"; + } + } else { + code += "_o->" + name + "[_i]" + access + " = "; + code += GenUnpackVal(field.value.type.VectorType(), indexing, true, + field); + } + code += "; } }"; } - code += "; } }"; break; } case BASE_TYPE_UTYPE: { diff --git a/tests/cpp17/generated_cpp17/monster_test_generated.h b/tests/cpp17/generated_cpp17/monster_test_generated.h index baa3a6e1e..6ec52bf30 100644 --- a/tests/cpp17/generated_cpp17/monster_test_generated.h +++ b/tests/cpp17/generated_cpp17/monster_test_generated.h @@ -2298,7 +2298,7 @@ inline void Monster::UnPackTo(MonsterT *_o, const flatbuffers::resolver_function { auto _e = mana(); _o->mana = _e; } { auto _e = hp(); _o->hp = _e; } { auto _e = name(); if (_e) _o->name = _e->str(); } - { auto _e = inventory(); if (_e) { _o->inventory.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->inventory[_i] = _e->Get(_i); } } } + { auto _e = inventory(); if (_e) { _o->inventory.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->inventory.begin()); } } { auto _e = color(); _o->color = _e; } { auto _e = test_type(); _o->test.type = _e; } { auto _e = test(); if (_e) _o->test.value = MyGame::Example::AnyUnion::UnPack(_e, test_type(), _resolver); } @@ -2306,7 +2306,7 @@ inline void Monster::UnPackTo(MonsterT *_o, const flatbuffers::resolver_function { auto _e = testarrayofstring(); if (_e) { _o->testarrayofstring.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->testarrayofstring[_i] = _e->Get(_i)->str(); } } } { auto _e = testarrayoftables(); if (_e) { _o->testarrayoftables.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->testarrayoftables[_i] = std::unique_ptr(_e->Get(_i)->UnPack(_resolver)); } } } { auto _e = enemy(); if (_e) _o->enemy = std::unique_ptr(_e->UnPack(_resolver)); } - { auto _e = testnestedflatbuffer(); if (_e) { _o->testnestedflatbuffer.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->testnestedflatbuffer[_i] = _e->Get(_i); } } } + { auto _e = testnestedflatbuffer(); if (_e) { _o->testnestedflatbuffer.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->testnestedflatbuffer.begin()); } } { auto _e = testempty(); if (_e) _o->testempty = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = testbool(); _o->testbool = _e; } { auto _e = testhashs32_fnv1(); _o->testhashs32_fnv1 = _e; } @@ -2324,7 +2324,7 @@ if (_resolver) (*_resolver)(reinterpret_cast(&_o->testhashu32_fnv1a), s { auto _e = testf3(); _o->testf3 = _e; } { auto _e = testarrayofstring2(); if (_e) { _o->testarrayofstring2.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->testarrayofstring2[_i] = _e->Get(_i)->str(); } } } { auto _e = testarrayofsortedstruct(); if (_e) { _o->testarrayofsortedstruct.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->testarrayofsortedstruct[_i] = *_e->Get(_i); } } } - { auto _e = flex(); if (_e) { _o->flex.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->flex[_i] = _e->Get(_i); } } } + { auto _e = flex(); if (_e) { _o->flex.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->flex.begin()); } } { auto _e = test5(); if (_e) { _o->test5.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->test5[_i] = *_e->Get(_i); } } } { auto _e = vector_of_longs(); if (_e) { _o->vector_of_longs.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->vector_of_longs[_i] = _e->Get(_i); } } } { auto _e = vector_of_doubles(); if (_e) { _o->vector_of_doubles.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->vector_of_doubles[_i] = _e->Get(_i); } } } @@ -2478,7 +2478,7 @@ inline void TypeAliases::UnPackTo(TypeAliasesT *_o, const flatbuffers::resolver_ { auto _e = u64(); _o->u64 = _e; } { auto _e = f32(); _o->f32 = _e; } { auto _e = f64(); _o->f64 = _e; } - { auto _e = v8(); if (_e) { _o->v8.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->v8[_i] = _e->Get(_i); } } } + { auto _e = v8(); if (_e) { _o->v8.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->v8.begin()); } } { auto _e = vf64(); if (_e) { _o->vf64.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->vf64[_i] = _e->Get(_i); } } } } diff --git a/tests/monster_test_generated.h b/tests/monster_test_generated.h index b932622bf..98830af15 100644 --- a/tests/monster_test_generated.h +++ b/tests/monster_test_generated.h @@ -2580,7 +2580,7 @@ inline void Monster::UnPackTo(MonsterT *_o, const flatbuffers::resolver_function { auto _e = mana(); _o->mana = _e; } { auto _e = hp(); _o->hp = _e; } { auto _e = name(); if (_e) _o->name = _e->str(); } - { auto _e = inventory(); if (_e) { _o->inventory.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->inventory[_i] = _e->Get(_i); } } } + { auto _e = inventory(); if (_e) { _o->inventory.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->inventory.begin()); } } { auto _e = color(); _o->color = _e; } { auto _e = test_type(); _o->test.type = _e; } { auto _e = test(); if (_e) _o->test.value = MyGame::Example::AnyUnion::UnPack(_e, test_type(), _resolver); } @@ -2588,7 +2588,7 @@ inline void Monster::UnPackTo(MonsterT *_o, const flatbuffers::resolver_function { auto _e = testarrayofstring(); if (_e) { _o->testarrayofstring.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->testarrayofstring[_i] = _e->Get(_i)->str(); } } } { auto _e = testarrayoftables(); if (_e) { _o->testarrayoftables.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->testarrayoftables[_i] = flatbuffers::unique_ptr(_e->Get(_i)->UnPack(_resolver)); } } } { auto _e = enemy(); if (_e) _o->enemy = flatbuffers::unique_ptr(_e->UnPack(_resolver)); } - { auto _e = testnestedflatbuffer(); if (_e) { _o->testnestedflatbuffer.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->testnestedflatbuffer[_i] = _e->Get(_i); } } } + { auto _e = testnestedflatbuffer(); if (_e) { _o->testnestedflatbuffer.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->testnestedflatbuffer.begin()); } } { auto _e = testempty(); if (_e) _o->testempty = flatbuffers::unique_ptr(_e->UnPack(_resolver)); } { auto _e = testbool(); _o->testbool = _e; } { auto _e = testhashs32_fnv1(); _o->testhashs32_fnv1 = _e; } @@ -2606,7 +2606,7 @@ if (_resolver) (*_resolver)(reinterpret_cast(&_o->testhashu32_fnv1a), s { auto _e = testf3(); _o->testf3 = _e; } { auto _e = testarrayofstring2(); if (_e) { _o->testarrayofstring2.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->testarrayofstring2[_i] = _e->Get(_i)->str(); } } } { auto _e = testarrayofsortedstruct(); if (_e) { _o->testarrayofsortedstruct.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->testarrayofsortedstruct[_i] = *_e->Get(_i); } } } - { auto _e = flex(); if (_e) { _o->flex.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->flex[_i] = _e->Get(_i); } } } + { auto _e = flex(); if (_e) { _o->flex.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->flex.begin()); } } { auto _e = test5(); if (_e) { _o->test5.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->test5[_i] = *_e->Get(_i); } } } { auto _e = vector_of_longs(); if (_e) { _o->vector_of_longs.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->vector_of_longs[_i] = _e->Get(_i); } } } { auto _e = vector_of_doubles(); if (_e) { _o->vector_of_doubles.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->vector_of_doubles[_i] = _e->Get(_i); } } } @@ -2760,7 +2760,7 @@ inline void TypeAliases::UnPackTo(TypeAliasesT *_o, const flatbuffers::resolver_ { auto _e = u64(); _o->u64 = _e; } { auto _e = f32(); _o->f32 = _e; } { auto _e = f64(); _o->f64 = _e; } - { auto _e = v8(); if (_e) { _o->v8.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->v8[_i] = _e->Get(_i); } } } + { auto _e = v8(); if (_e) { _o->v8.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->v8.begin()); } } { auto _e = vf64(); if (_e) { _o->vf64.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->vf64[_i] = _e->Get(_i); } } } }