Optimize marshalTags().

This commit optimizes the marshaling of tag sets. Previously the
strings.Join() function was used with added a lot of memory allocations.
The new implementation manually allocates and appends to a byte slice.

Previous implementation:

$ go test -run=^\$ -bench=BenchmarkMarshalTags -v
PASS
BenchmarkMarshalTags_KeyN1	 2000000	       824 ns/op	     176 B/op	       6 allocs/op
BenchmarkMarshalTags_KeyN3	 1000000	      1319 ns/op	     416 B/op	       6 allocs/op
BenchmarkMarshalTags_KeyN5	 1000000	      2173 ns/op	     656 B/op	       6 allocs/op
BenchmarkMarshalTags_KeyN10	  500000	      3929 ns/op	    1280 B/op	       6 allocs/op

New implementation:

$ go test -run=^\$ -bench=BenchmarkMarshalTags -v
PASS
BenchmarkMarshalTags_KeyN1	 3000000	       458 ns/op	      80 B/op	       3 allocs/op
BenchmarkMarshalTags_KeyN3	 2000000	       753 ns/op	     160 B/op	       3 allocs/op
BenchmarkMarshalTags_KeyN5	 1000000	      1193 ns/op	     240 B/op	       3 allocs/op
BenchmarkMarshalTags_KeyN10	  500000	      2477 ns/op	     448 B/op	       3 allocs/op
pull/2131/head
Ben Johnson 2015-03-31 16:55:42 -06:00
parent 94a4baf34e
commit 04a475cdb2
2 changed files with 77 additions and 9 deletions

View File

@ -1308,18 +1308,37 @@ func (db *database) continuousQueryByName(name string) *ContinuousQuery {
// used to convert the tag set to bytes for use as a lookup key
func marshalTags(tags map[string]string) []byte {
s := make([]string, 0, len(tags))
// pull out keys to sort
for k := range tags {
s = append(s, k)
// Empty maps marshal to empty bytes.
if len(tags) == 0 {
return nil
}
sort.Strings(s)
// now append on the key values in key sorted order
for _, k := range s {
s = append(s, tags[k])
// Extract keys and determine final size.
sz := (len(tags) * 2) - 1 // separators
keys := make([]string, 0, len(tags))
for k, v := range tags {
keys = append(keys, k)
sz += len(k) + len(v)
}
return []byte(strings.Join(s, "|"))
sort.Strings(keys)
// Generate marshaled bytes.
b := make([]byte, sz)
buf := b
for _, k := range keys {
copy(buf, k)
buf[len(k)] = '|'
buf = buf[len(k)+1:]
}
for i, k := range keys {
v := tags[k]
copy(buf, v)
if i < len(keys)-1 {
buf[len(v)] = '|'
buf = buf[len(v)+1:]
}
}
return b
}
// timeBetweenInclusive returns true if t is between min and max, inclusive.

View File

@ -3,6 +3,8 @@ package influxdb
// This file is run within the "influxdb" package and allows for internal unit tests.
import (
"bytes"
"fmt"
"reflect"
"testing"
"time"
@ -310,6 +312,53 @@ func TestShardGroup_Contains(t *testing.T) {
}
}
// Ensure tags can be marshaled into a byte slice.
func TestMarshalTags(t *testing.T) {
for i, tt := range []struct {
tags map[string]string
result []byte
}{
{
tags: nil,
result: nil,
},
{
tags: map[string]string{"foo": "bar"},
result: []byte(`foo|bar`),
},
{
tags: map[string]string{"foo": "bar", "baz": "battttt"},
result: []byte(`baz|foo|battttt|bar`),
},
} {
result := marshalTags(tt.tags)
if !bytes.Equal(result, tt.result) {
t.Fatalf("%d. unexpected result: exp=%s, got=%s", i, tt.result, result)
}
}
}
func BenchmarkMarshalTags_KeyN1(b *testing.B) { benchmarkMarshalTags(b, 1) }
func BenchmarkMarshalTags_KeyN3(b *testing.B) { benchmarkMarshalTags(b, 3) }
func BenchmarkMarshalTags_KeyN5(b *testing.B) { benchmarkMarshalTags(b, 5) }
func BenchmarkMarshalTags_KeyN10(b *testing.B) { benchmarkMarshalTags(b, 10) }
func benchmarkMarshalTags(b *testing.B, keyN int) {
const keySize, valueSize = 8, 15
// Generate tag map.
tags := make(map[string]string)
for i := 0; i < keyN; i++ {
tags[fmt.Sprintf("%0*d", keySize, i)] = fmt.Sprintf("%0*d", valueSize, i)
}
// Unmarshal map into byte slice.
b.ReportAllocs()
for i := 0; i < b.N; i++ {
marshalTags(tags)
}
}
// MustParseExpr parses an expression string and returns its AST representation.
func MustParseExpr(s string) influxql.Expr {
expr, err := influxql.ParseExpr(s)