Merge pull request #165 from rw/go-faster

Go speed improvements
This commit is contained in:
Robert 2015-05-12 14:53:31 -07:00
commit 4d213c2d06
7 changed files with 206 additions and 30 deletions

View File

@ -31,11 +31,38 @@ func NewBuilder(initialSize int) *Builder {
return b 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. // StartObject initializes bookkeeping for writing a new object.
func (b *Builder) StartObject(numfields int) { func (b *Builder) StartObject(numfields int) {
b.notNested() b.notNested()
// use 32-bit offsets so that arithmetic doesn't overflow. // 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.objectEnd = b.Offset()
b.minalign = 1 b.minalign = 1
} }
@ -137,7 +164,7 @@ func (b *Builder) WriteVtable() (n UOffsetT) {
SOffsetT(existingVtable)-SOffsetT(objectOffset)) SOffsetT(existingVtable)-SOffsetT(objectOffset))
} }
b.vtable = nil b.vtable = b.vtable[:0]
return objectOffset return objectOffset
} }
@ -155,13 +182,20 @@ func (b *Builder) growByteBuffer() {
if (int64(len(b.Bytes)) & int64(0xC0000000)) != 0 { if (int64(len(b.Bytes)) & int64(0xC0000000)) != 0 {
panic("cannot grow buffer beyond 2 gigabytes") panic("cannot grow buffer beyond 2 gigabytes")
} }
newSize := len(b.Bytes) * 2 newLen := len(b.Bytes) * 2
if newSize == 0 { if newLen == 0 {
newSize = 1 newLen = 1
} }
bytes2 := make([]byte, newSize)
copy(bytes2[newSize-len(b.Bytes):], b.Bytes) if cap(b.Bytes) >= newLen {
b.Bytes = bytes2 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. // 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. // CreateString writes a null-terminated string as a vector.
func (b *Builder) CreateString(s string) UOffsetT { 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.Prep(int(SizeUOffsetT), (len(s)+1)*SizeByte)
b.PlaceByte(0) b.PlaceByte(0)
x := []byte(s) l := UOffsetT(len(s))
l := UOffsetT(len(x))
b.head -= l 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 // CreateByteVector writes a ubyte vector
@ -274,7 +312,7 @@ func (b *Builder) CreateByteVector(v []byte) UOffsetT {
func (b *Builder) notNested() { func (b *Builder) notNested() {
// Check that no other objects are being built while making this // Check that no other objects are being built while making this
// object. If not, panic: // object. If not, panic:
if b.vtable != nil { if len(b.vtable) > 0 {
panic("non-inline data write inside of object") panic("non-inline data write inside of object")
} }
} }

View File

@ -238,7 +238,7 @@ static void GetStringField(const StructDef &struct_def,
code += " " + MakeCamel(field.name); code += " " + MakeCamel(field.name);
code += "() " + TypeName(field) + " "; code += "() " + TypeName(field) + " ";
code += OffsetPrefix(field) + "\t\treturn " + GenGetter(field.value.type); 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"; code += "}\n\n";
} }
@ -301,7 +301,7 @@ static void GetMemberOfVectorOfNonStruct(const StructDef &struct_def,
code += NumToString(InlineSize(vectortype)) + "))\n"; code += NumToString(InlineSize(vectortype)) + "))\n";
code += "\t}\n"; code += "\t}\n";
if (vectortype.base_type == BASE_TYPE_STRING) { if (vectortype.base_type == BASE_TYPE_STRING) {
code += "\treturn \"\"\n"; code += "\treturn nil\n";
} else { } else {
code += "\treturn 0\n"; 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. // Returns the function name that is able to read a value of the given type.
static std::string GenGetter(const Type &type) { static std::string GenGetter(const Type &type) {
switch (type.base_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_UNION: return "rcv._tab.Union";
case BASE_TYPE_VECTOR: return GenGetter(type.VectorType()); case BASE_TYPE_VECTOR: return GenGetter(type.VectorType());
default: default:
@ -626,7 +626,7 @@ static std::string GenTypeBasic(const Type &type) {
static std::string GenTypePointer(const Type &type) { static std::string GenTypePointer(const Type &type) {
switch (type.base_type) { switch (type.base_type) {
case BASE_TYPE_STRING: case BASE_TYPE_STRING:
return "string"; return "[]byte";
case BASE_TYPE_VECTOR: case BASE_TYPE_VECTOR:
return GenTypeGet(type.VectorType()); return GenTypeGet(type.VectorType());
case BASE_TYPE_STRUCT: case BASE_TYPE_STRUCT:

View File

@ -971,6 +971,8 @@ void Parser::ParseDecl() {
CheckClash("Type", BASE_TYPE_UNION); CheckClash("Type", BASE_TYPE_UNION);
CheckClash("_length", BASE_TYPE_VECTOR); CheckClash("_length", BASE_TYPE_VECTOR);
CheckClash("Length", BASE_TYPE_VECTOR); CheckClash("Length", BASE_TYPE_VECTOR);
CheckClash("_byte_vector", BASE_TYPE_STRING);
CheckClash("ByteVector", BASE_TYPE_STRING);
Expect('}'); Expect('}');
} }

2
tests/GoTest.sh Normal file → Executable file
View File

@ -43,6 +43,8 @@ GOPATH=${go_path} go test flatbuffers_test \
--test.coverpkg=github.com/google/flatbuffers/go \ --test.coverpkg=github.com/google/flatbuffers/go \
--cpp_data=${test_dir}/monsterdata_test.mon \ --cpp_data=${test_dir}/monsterdata_test.mon \
--out_data=${test_dir}/monsterdata_go_wire.mon \ --out_data=${test_dir}/monsterdata_go_wire.mon \
--test.bench=. \
--test.benchtime=3s \
--fuzz=true \ --fuzz=true \
--fuzz_fields=4 \ --fuzz_fields=4 \
--fuzz_objects=10000 --fuzz_objects=10000

View File

@ -50,12 +50,12 @@ func (rcv *Monster) Hp() int16 {
return 100 return 100
} }
func (rcv *Monster) Name() string { func (rcv *Monster) Name() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) o := flatbuffers.UOffsetT(rcv._tab.Offset(10))
if o != 0 { 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 { func (rcv *Monster) Inventory(j int) byte {
@ -130,13 +130,13 @@ func (rcv *Monster) Test4Length() int {
return 0 return 0
} }
func (rcv *Monster) Testarrayofstring(j int) string { func (rcv *Monster) Testarrayofstring(j int) []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(24)) o := flatbuffers.UOffsetT(rcv._tab.Offset(24))
if o != 0 { if o != 0 {
a := rcv._tab.Vector(o) 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 { func (rcv *Monster) TestarrayofstringLength() int {

View File

@ -14,12 +14,12 @@ func (rcv *Stat) Init(buf []byte, i flatbuffers.UOffsetT) {
rcv._tab.Pos = i rcv._tab.Pos = i
} }
func (rcv *Stat) Id() string { func (rcv *Stat) Id() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
if o != 0 { 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 { func (rcv *Stat) Val() int64 {

View File

@ -132,7 +132,7 @@ func CheckReadBuffer(buf []byte, offset flatbuffers.UOffsetT, fail func(string,
fail(FailString("mana", 150, got)) 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)) fail(FailString("name", "MyMonster", got))
} }
@ -210,7 +210,7 @@ func CheckReadBuffer(buf []byte, offset flatbuffers.UOffsetT, fail func(string,
var monster2 example.Monster var monster2 example.Monster
monster2.Init(table2.Bytes, table2.Pos) 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)) 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)) 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)) 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)) 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 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}) 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 // test 7: empty vtable
b = flatbuffers.NewBuilder(0) b = flatbuffers.NewBuilder(0)
b.StartObject(0) b.StartObject(0)
@ -1185,3 +1194,128 @@ func BenchmarkVtableDeduplication(b *testing.B) {
builder.EndObject() 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)
}
}