diff --git a/go/builder.go b/go/builder.go index 590c0682b..30b09cf9d 100644 --- a/go/builder.go +++ b/go/builder.go @@ -31,11 +31,38 @@ func NewBuilder(initialSize int) *Builder { return b } +// Reset truncates the underlying Builder buffer, facilitating alloc-free +// reuse of a Builder. +func (b *Builder) Reset() { + if b.Bytes != nil { + b.Bytes = b.Bytes[:cap(b.Bytes)] + } + + if b.vtables != nil { + b.vtables = b.vtables[:0] + } + + if b.vtable != nil { + b.vtable = b.vtable[:0] + } + + b.head = UOffsetT(len(b.Bytes)) + b.minalign = 1 +} + // StartObject initializes bookkeeping for writing a new object. func (b *Builder) StartObject(numfields int) { b.notNested() // use 32-bit offsets so that arithmetic doesn't overflow. - b.vtable = make([]UOffsetT, numfields) + if cap(b.vtable) < numfields || b.vtable == nil { + b.vtable = make([]UOffsetT, numfields) + } else { + b.vtable = b.vtable[:numfields] + for i := 0; i < len(b.vtable); i++ { + b.vtable[i] = 0 + } + } + b.objectEnd = b.Offset() b.minalign = 1 } @@ -137,7 +164,7 @@ func (b *Builder) WriteVtable() (n UOffsetT) { SOffsetT(existingVtable)-SOffsetT(objectOffset)) } - b.vtable = nil + b.vtable = b.vtable[:0] return objectOffset } @@ -155,13 +182,20 @@ func (b *Builder) growByteBuffer() { if (int64(len(b.Bytes)) & int64(0xC0000000)) != 0 { panic("cannot grow buffer beyond 2 gigabytes") } - newSize := len(b.Bytes) * 2 - if newSize == 0 { - newSize = 1 + newLen := len(b.Bytes) * 2 + if newLen == 0 { + newLen = 1 } - bytes2 := make([]byte, newSize) - copy(bytes2[newSize-len(b.Bytes):], b.Bytes) - b.Bytes = bytes2 + + if cap(b.Bytes) >= newLen { + b.Bytes = b.Bytes[:newLen] + } else { + extension := make([]byte, newLen-len(b.Bytes)) + b.Bytes = append(b.Bytes, extension...) + } + + middle := newLen / 2 + copy(b.Bytes[middle:], b.Bytes[:middle]) } // Head gives the start of useful data in the underlying byte buffer. @@ -247,16 +281,20 @@ func (b *Builder) EndVector(vectorNumElems int) UOffsetT { // CreateString writes a null-terminated string as a vector. func (b *Builder) CreateString(s string) UOffsetT { + return b.CreateByteString([]byte(s)) +} + +// CreateByteString writes a byte slice as a string (null-terminated). +func (b *Builder) CreateByteString(s []byte) UOffsetT { b.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte) b.PlaceByte(0) - x := []byte(s) - l := UOffsetT(len(x)) + l := UOffsetT(len(s)) b.head -= l - copy(b.Bytes[b.head:b.head+l], x) + copy(b.Bytes[b.head:b.head+l], s) - return b.EndVector(len(x)) + return b.EndVector(len(s)) } // CreateByteVector writes a ubyte vector @@ -274,7 +312,7 @@ func (b *Builder) CreateByteVector(v []byte) UOffsetT { func (b *Builder) notNested() { // Check that no other objects are being built while making this // object. If not, panic: - if b.vtable != nil { + if len(b.vtable) > 0 { panic("non-inline data write inside of object") } } diff --git a/src/idl_gen_go.cpp b/src/idl_gen_go.cpp index 9cbaf99ba..978f42fff 100644 --- a/src/idl_gen_go.cpp +++ b/src/idl_gen_go.cpp @@ -238,7 +238,7 @@ static void GetStringField(const StructDef &struct_def, code += " " + MakeCamel(field.name); code += "() " + TypeName(field) + " "; code += OffsetPrefix(field) + "\t\treturn " + GenGetter(field.value.type); - code += "(o + rcv._tab.Pos)\n\t}\n\treturn \"\"\n"; + code += "(o + rcv._tab.Pos)\n\t}\n\treturn nil\n"; code += "}\n\n"; } @@ -301,7 +301,7 @@ static void GetMemberOfVectorOfNonStruct(const StructDef &struct_def, code += NumToString(InlineSize(vectortype)) + "))\n"; code += "\t}\n"; if (vectortype.base_type == BASE_TYPE_STRING) { - code += "\treturn \"\"\n"; + code += "\treturn nil\n"; } else { code += "\treturn 0\n"; } @@ -573,7 +573,7 @@ static void GenEnum(const EnumDef &enum_def, std::string *code_ptr) { // Returns the function name that is able to read a value of the given type. static std::string GenGetter(const Type &type) { switch (type.base_type) { - case BASE_TYPE_STRING: return "rcv._tab.String"; + case BASE_TYPE_STRING: return "rcv._tab.ByteVector"; case BASE_TYPE_UNION: return "rcv._tab.Union"; case BASE_TYPE_VECTOR: return GenGetter(type.VectorType()); default: @@ -626,7 +626,7 @@ static std::string GenTypeBasic(const Type &type) { static std::string GenTypePointer(const Type &type) { switch (type.base_type) { case BASE_TYPE_STRING: - return "string"; + return "[]byte"; case BASE_TYPE_VECTOR: return GenTypeGet(type.VectorType()); case BASE_TYPE_STRUCT: diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index 3799fd177..158ffcaf0 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -971,6 +971,8 @@ void Parser::ParseDecl() { CheckClash("Type", BASE_TYPE_UNION); CheckClash("_length", BASE_TYPE_VECTOR); CheckClash("Length", BASE_TYPE_VECTOR); + CheckClash("_byte_vector", BASE_TYPE_STRING); + CheckClash("ByteVector", BASE_TYPE_STRING); Expect('}'); } diff --git a/tests/GoTest.sh b/tests/GoTest.sh old mode 100644 new mode 100755 index ba4835a20..f9cc41040 --- a/tests/GoTest.sh +++ b/tests/GoTest.sh @@ -43,6 +43,8 @@ GOPATH=${go_path} go test flatbuffers_test \ --test.coverpkg=github.com/google/flatbuffers/go \ --cpp_data=${test_dir}/monsterdata_test.mon \ --out_data=${test_dir}/monsterdata_go_wire.mon \ + --test.bench=. \ + --test.benchtime=3s \ --fuzz=true \ --fuzz_fields=4 \ --fuzz_objects=10000 diff --git a/tests/MyGame/Example/Monster.go b/tests/MyGame/Example/Monster.go index 0a6ba135e..a51e42966 100644 --- a/tests/MyGame/Example/Monster.go +++ b/tests/MyGame/Example/Monster.go @@ -50,12 +50,12 @@ func (rcv *Monster) Hp() int16 { return 100 } -func (rcv *Monster) Name() string { +func (rcv *Monster) Name() []byte { o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) if o != 0 { - return rcv._tab.String(o + rcv._tab.Pos) + return rcv._tab.ByteVector(o + rcv._tab.Pos) } - return "" + return nil } func (rcv *Monster) Inventory(j int) byte { @@ -130,13 +130,13 @@ func (rcv *Monster) Test4Length() int { return 0 } -func (rcv *Monster) Testarrayofstring(j int) string { +func (rcv *Monster) Testarrayofstring(j int) []byte { o := flatbuffers.UOffsetT(rcv._tab.Offset(24)) if o != 0 { a := rcv._tab.Vector(o) - return rcv._tab.String(a + flatbuffers.UOffsetT(j * 4)) + return rcv._tab.ByteVector(a + flatbuffers.UOffsetT(j * 4)) } - return "" + return nil } func (rcv *Monster) TestarrayofstringLength() int { diff --git a/tests/MyGame/Example/Stat.go b/tests/MyGame/Example/Stat.go index b2c8e3e18..936d5ec9c 100644 --- a/tests/MyGame/Example/Stat.go +++ b/tests/MyGame/Example/Stat.go @@ -14,12 +14,12 @@ func (rcv *Stat) Init(buf []byte, i flatbuffers.UOffsetT) { rcv._tab.Pos = i } -func (rcv *Stat) Id() string { +func (rcv *Stat) Id() []byte { o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) if o != 0 { - return rcv._tab.String(o + rcv._tab.Pos) + return rcv._tab.ByteVector(o + rcv._tab.Pos) } - return "" + return nil } func (rcv *Stat) Val() int64 { diff --git a/tests/go_test.go b/tests/go_test.go index 08e823c75..2f03ca61c 100644 --- a/tests/go_test.go +++ b/tests/go_test.go @@ -132,7 +132,7 @@ func CheckReadBuffer(buf []byte, offset flatbuffers.UOffsetT, fail func(string, fail(FailString("mana", 150, got)) } - if got := monster.Name(); "MyMonster" != got { + if got := monster.Name(); !bytes.Equal([]byte("MyMonster"), got) { fail(FailString("name", "MyMonster", got)) } @@ -210,7 +210,7 @@ func CheckReadBuffer(buf []byte, offset flatbuffers.UOffsetT, fail func(string, var monster2 example.Monster monster2.Init(table2.Bytes, table2.Pos) - if got := monster2.Name(); "Fred" != got { + if got := monster2.Name(); !bytes.Equal([]byte("Fred"), got) { fail(FailString("monster2.Name()", "Fred", got)) } @@ -268,11 +268,11 @@ func CheckReadBuffer(buf []byte, offset flatbuffers.UOffsetT, fail func(string, fail(FailString("Testarrayofstring length", 2, got)) } - if got := monster.Testarrayofstring(0); "test1" != got { + if got := monster.Testarrayofstring(0); !bytes.Equal([]byte("test1"), got) { fail(FailString("Testarrayofstring(0)", "test1", got)) } - if got := monster.Testarrayofstring(1); "test2" != got { + if got := monster.Testarrayofstring(1); !bytes.Equal([]byte("test2"), got) { fail(FailString("Testarrayofstring(1)", "test2", got)) } } @@ -533,6 +533,15 @@ func CheckByteLayout(fail func(string, ...interface{})) { check([]byte{4, 0, 0, 0, 'm', 'o', 'o', 'p', 0, 0, 0, 0, // 0-terminated, 3-byte pad 3, 0, 0, 0, 'f', 'o', 'o', 0}) + // test 6b: CreateByteString + + b = flatbuffers.NewBuilder(0) + b.CreateByteString([]byte("foo")) + check([]byte{3, 0, 0, 0, 'f', 'o', 'o', 0}) // 0-terminated, no pad + b.CreateByteString([]byte("moop")) + check([]byte{4, 0, 0, 0, 'm', 'o', 'o', 'p', 0, 0, 0, 0, // 0-terminated, 3-byte pad + 3, 0, 0, 0, 'f', 'o', 'o', 0}) + // test 7: empty vtable b = flatbuffers.NewBuilder(0) b.StartObject(0) @@ -1185,3 +1194,128 @@ func BenchmarkVtableDeduplication(b *testing.B) { builder.EndObject() } } + +// BenchmarkParseGold measures the speed of parsing the 'gold' data +// used throughout this test suite. +func BenchmarkParseGold(b *testing.B) { + buf, offset := CheckGeneratedBuild(b.Fatalf) + monster := example.GetRootAsMonster(buf, offset) + + // use these to prevent allocations: + reuse_pos := example.Vec3{} + reuse_test3 := example.Test{} + reuse_table2 := flatbuffers.Table{} + reuse_monster2 := example.Monster{} + reuse_test4_0 := example.Test{} + reuse_test4_1 := example.Test{} + + b.SetBytes(int64(len(buf[offset:]))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + monster.Hp() + monster.Mana() + name := monster.Name() + _ = name[0] + _ = name[len(name)-1] + + monster.Pos(&reuse_pos) + reuse_pos.X() + reuse_pos.Y() + reuse_pos.Z() + reuse_pos.Test1() + reuse_pos.Test2() + reuse_pos.Test3(&reuse_test3) + reuse_test3.A() + reuse_test3.B() + monster.TestType() + monster.Test(&reuse_table2) + reuse_monster2.Init(reuse_table2.Bytes, reuse_table2.Pos) + name2 := reuse_monster2.Name() + _ = name2[0] + _ = name2[len(name2)-1] + monster.InventoryLength() + l := monster.InventoryLength() + for i := 0; i < l; i++ { + monster.Inventory(i) + } + monster.Test4Length() + monster.Test4(&reuse_test4_0, 0) + monster.Test4(&reuse_test4_1, 1) + + reuse_test4_0.A() + reuse_test4_0.B() + reuse_test4_1.A() + reuse_test4_1.B() + + monster.TestarrayofstringLength() + str0 := monster.Testarrayofstring(0) + _ = str0[0] + _ = str0[len(str0)-1] + str1 := monster.Testarrayofstring(1) + _ = str1[0] + _ = str1[len(str1)-1] + } +} + +// BenchmarkBuildGold uses generated code to build the example Monster. +func BenchmarkBuildGold(b *testing.B) { + buf, offset := CheckGeneratedBuild(b.Fatalf) + bytes_length := int64(len(buf[offset:])) + + reuse_str := []byte("MyMonster") + reuse_test1 := []byte("test1") + reuse_test2 := []byte("test2") + reuse_fred := []byte("Fred") + + b.SetBytes(bytes_length) + bldr := flatbuffers.NewBuilder(0) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + bldr.Reset() + + str := bldr.CreateByteString(reuse_str) + test1 := bldr.CreateByteString(reuse_test1) + test2 := bldr.CreateByteString(reuse_test2) + fred := bldr.CreateByteString(reuse_fred) + + example.MonsterStartInventoryVector(bldr, 5) + bldr.PrependByte(4) + bldr.PrependByte(3) + bldr.PrependByte(2) + bldr.PrependByte(1) + bldr.PrependByte(0) + inv := bldr.EndVector(5) + + example.MonsterStart(bldr) + example.MonsterAddName(bldr, fred) + mon2 := example.MonsterEnd(bldr) + + example.MonsterStartTest4Vector(bldr, 2) + example.CreateTest(bldr, 10, 20) + example.CreateTest(bldr, 30, 40) + test4 := bldr.EndVector(2) + + example.MonsterStartTestarrayofstringVector(bldr, 2) + bldr.PrependUOffsetT(test2) + bldr.PrependUOffsetT(test1) + testArrayOfString := bldr.EndVector(2) + + example.MonsterStart(bldr) + + pos := example.CreateVec3(bldr, 1.0, 2.0, 3.0, 3.0, 2, 5, 6) + example.MonsterAddPos(bldr, pos) + + example.MonsterAddHp(bldr, 80) + example.MonsterAddName(bldr, str) + example.MonsterAddInventory(bldr, inv) + example.MonsterAddTestType(bldr, 1) + example.MonsterAddTest(bldr, mon2) + example.MonsterAddTest4(bldr, test4) + example.MonsterAddTestarrayofstring(bldr, testArrayOfString) + mon := example.MonsterEnd(bldr) + + bldr.Finish(mon) + } +}