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/oppull/2131/head
parent
94a4baf34e
commit
04a475cdb2
37
database.go
37
database.go
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue