Skip to content

Commit

Permalink
perf: use span cache for read
Browse files Browse the repository at this point in the history
  • Loading branch information
ppzqh committed May 29, 2024
1 parent 6fc453d commit d6d824b
Show file tree
Hide file tree
Showing 19 changed files with 396 additions and 220 deletions.
212 changes: 109 additions & 103 deletions benchmark/fastpb_gen/testcase.pb.fast.go

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions benchmark/fastpb_gen/testcase.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion benchmark/gen_code.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ protoc --go_opt=paths=source_relative --go_out=./fastpb_gen --fastpb_opt=paths=s

# gogo
rm -rf ./gogo_gen && mkdir ./gogo_gen
protoc --gogofaster_opt=paths=source_relative --gogofaster_out=./gogo_gen -I ./idl/ ./idl/testcase.proto
protoc --gofast_opt=paths=source_relative --gofast_out=./gogo_gen -I ./idl/ ./idl/testcase.proto
183 changes: 114 additions & 69 deletions benchmark/gogo_gen/testcase.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions examples/fastpb_gen/base.pb.fast.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions examples/fastpb_gen/base.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/fastpb_gen/echo.pb.fast.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions examples/fastpb_gen/echo.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/fastpb_gen/nested/nested.pb.fast.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions examples/fastpb_gen/nested/nested.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/fastpb_gen/pet/pet.pb.fast.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions examples/fastpb_gen/pet/pet.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/fastpb_gen/user/user.pb.fast.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions examples/fastpb_gen/user/user.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 9 additions & 4 deletions fastpb_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"math"
"unicode/utf8"

"github.com/cloudwego/fastpb/mem"

"google.golang.org/protobuf/encoding/protowire"
)

Expand All @@ -31,7 +33,10 @@ var Impl impl
// to make room).
const speculativeLength = 1

var _ Protocol = impl{}
var (
_ Protocol = impl{}
spanCache = mem.NewSpanCache(1024 * 1024) // 1MB
)

type impl struct{}

Expand Down Expand Up @@ -412,7 +417,8 @@ func (b impl) ReadString(buf []byte, _type int8) (value string, n int, err error
if EnforceUTF8() && !utf8.Valid(v) {
return value, 0, errInvalidUTF8
}
return string(v), n, nil
value = SliceByteToString(spanCache.Copy(v))
return value, n, nil
}

// ReadBytes .
Expand All @@ -425,8 +431,7 @@ func (b impl) ReadBytes(buf []byte, _type int8) (value []byte, n int, err error)
if n < 0 {
return value, 0, errDecode
}
value = make([]byte, len(v))
copy(value, v)
value = spanCache.Copy(v)
return value, n, nil
}

Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ module github.com/cloudwego/fastpb
go 1.17

require (
github.com/gogo/protobuf v1.3.2
github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1
github.com/golang/protobuf v1.5.0
google.golang.org/protobuf v1.28.0
)

require golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
43 changes: 15 additions & 28 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,38 +1,25 @@
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1 h1:rT7Mm6uUpHeZQzfs2v0Mlj0SL02CzyVi+EB7VYPM/z4=
github.com/bytedance/gopkg v0.0.0-20240514070511-01b2cbcf35e1/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
110 changes: 110 additions & 0 deletions mem/span.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2024 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package mem

import (
"math/bits"
"sync/atomic"

"github.com/bytedance/gopkg/lang/dirtmake"
)

const (
spanCacheSize = 10
minSpanObject = 128 // 128 B
maxSpanObject = (minSpanObject << spanCacheSize) - 1 // 128 KB
minSpanClass = 8 // = spanClass(minSpanObject)
)

type spanCache struct {
spans [spanCacheSize]*span
}

// NewSpanCache returns *spanCache with the given spanSize,
// each span is used to allocate a binary of a specific size level.
func NewSpanCache(spanSize int) *spanCache {
c := new(spanCache)
for i := 0; i < len(c.spans); i++ {
c.spans[i] = NewSpan(spanSize)
}
return c
}

// Make allocates a binary but does not clear the memory it references.
// NOTE: MUST set any byte element before it's read.
func (c *spanCache) Make(n int) []byte {
sclass := spanClass(n) - minSpanClass
if sclass < 0 || sclass >= len(c.spans) {
return dirtmake.Bytes(n, n)
}
return c.spans[sclass].Make(n)
}

func (c *spanCache) Copy(buf []byte) (p []byte) {
p = c.Make(len(buf))
copy(p, buf)
return p
}

func NewSpan(size int) *span {
sp := new(span)
sp.size = uint32(size)
sp.buffer = dirtmake.Bytes(0, size)
return sp
}

type span struct {
lock uint32
read uint32 // read index of buffer
size uint32 // size of buffer
buffer []byte
}

func (b *span) Make(_n int) []byte {
n := uint32(_n)
if n >= b.size || !atomic.CompareAndSwapUint32(&b.lock, 0, 1) {
// fallback path: make a new byte slice if current goroutine cannot get the lock or n is out of size
return dirtmake.Bytes(int(n), int(n))
}
START:
b.read += n
// fast path
if b.read <= b.size {
buf := b.buffer[b.read-n : b.read : b.read]
atomic.StoreUint32(&b.lock, 0)
return buf
}
// slow path: create a new buffer
b.buffer = dirtmake.Bytes(int(b.size), int(b.size))
b.read = 0
goto START
}

func (b *span) Copy(buf []byte) (p []byte) {
p = b.Make(len(buf))
copy(p, buf)
return p
}

// spanClass calc the minimum number of bits required to represent x
// [2^sclass,2^(sclass+1)) bytes in a same span class
func spanClass(size int) int {
if size == 0 {
return 0
}
return bits.Len(uint(size))
}
9 changes: 9 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package fastpb

import "unsafe"

// SliceByteToString converts []byte to string without copy.
// DO NOT USE unless you know what you're doing.
func SliceByteToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}

0 comments on commit d6d824b

Please sign in to comment.