mirror of https://github.com/golang/go.git
[dev.fuzz] internal/fuzz: reduce allocation in the mutator
When mutating a byte slice, mutate in place, and only allocate once if
the slice's capacity is less than the maximum size.
mutateBytes already should not allocate; we check a post-condition
that the slice's data pointer does not change.
This speeds up the mutator from 4 ms per value to 200-600 ns. For
example:
goos: darwin
goarch: amd64
pkg: internal/fuzz
cpu: Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz
BenchmarkMutatorBytes/1-8 5908735 275.3 ns/op
BenchmarkMutatorBytes/10-8 5198473 282.0 ns/op
BenchmarkMutatorBytes/100-8 4304750 233.9 ns/op
BenchmarkMutatorBytes/1000-8 4623988 295.2 ns/op
BenchmarkMutatorBytes/10000-8 4252104 458.5 ns/op
BenchmarkMutatorBytes/100000-8 1236751 950.8 ns/op
PASS
ok internal/fuzz 12.993s
Change-Id: I4bf2a04be6c648ef440af2c62bf0ffa3d310172c
Reviewed-on: https://go-review.googlesource.com/c/go/+/306675
Trust: Jay Conrod <jayconrod@google.com>
Trust: Katie Hockman <katie@golang.org>
Run-TryBot: Jay Conrod <jayconrod@google.com>
Reviewed-by: Katie Hockman <katie@golang.org>
This commit is contained in:
parent
9d4d5ee66d
commit
b975d0baa0
|
|
@ -51,7 +51,6 @@ func CoordinateFuzzing(ctx context.Context, parallel int, seed []CorpusEntry, ty
|
|||
parallel = runtime.GOMAXPROCS(0)
|
||||
}
|
||||
|
||||
sharedMemSize := 100 << 20 // 100 MB
|
||||
// Make sure all of the seed corpus has marshalled data.
|
||||
for i := range seed {
|
||||
if seed[i].Data == nil {
|
||||
|
|
@ -85,7 +84,7 @@ func CoordinateFuzzing(ctx context.Context, parallel int, seed []CorpusEntry, ty
|
|||
errC := make(chan error)
|
||||
|
||||
newWorker := func() (*worker, error) {
|
||||
mem, err := sharedMemTempFile(sharedMemSize)
|
||||
mem, err := sharedMemTempFile(workerSharedMemSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,8 +51,7 @@ func min(a, b int) int {
|
|||
}
|
||||
|
||||
// mutate performs several mutations on the provided values.
|
||||
func (m *mutator) mutate(vals []interface{}, maxBytes int) []interface{} {
|
||||
// TODO(jayconrod,katiehockman): use as few allocations as possible
|
||||
func (m *mutator) mutate(vals []interface{}, maxBytes int) {
|
||||
// TODO(katiehockman): pull some of these functions into helper methods and
|
||||
// test that each case is working as expected.
|
||||
// TODO(katiehockman): perform more types of mutations.
|
||||
|
|
@ -71,11 +70,11 @@ func (m *mutator) mutate(vals []interface{}, maxBytes int) []interface{} {
|
|||
if len(v) > maxPerVal {
|
||||
panic(fmt.Sprintf("cannot mutate bytes of length %d", len(v)))
|
||||
}
|
||||
b := make([]byte, 0, maxPerVal)
|
||||
b = append(b, v...)
|
||||
m.mutateBytes(&b)
|
||||
vals[i] = b
|
||||
return vals
|
||||
if cap(v) < maxPerVal {
|
||||
v = append(make([]byte, 0, maxPerVal), v...)
|
||||
}
|
||||
m.mutateBytes(&v)
|
||||
vals[i] = v
|
||||
default:
|
||||
panic(fmt.Sprintf("type not supported for mutating: %T", vals[i]))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fuzz
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkMutatorBytes(b *testing.B) {
|
||||
for _, size := range []int{
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
} {
|
||||
size := size
|
||||
b.Run(strconv.Itoa(size), func(b *testing.B) {
|
||||
vals := []interface{}{make([]byte, size)}
|
||||
m := newMutator()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.mutate(vals, workerSharedMemSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,10 @@ const (
|
|||
// This distinguishes internal errors from uncontrolled panics and other crashes.
|
||||
// Keep in sync with internal/fuzz.workerExitCode.
|
||||
workerExitCode = 70
|
||||
|
||||
// workerSharedMemSize is the maximum size of the shared memory file used to
|
||||
// communicate with workers. This limits the size of fuzz inputs.
|
||||
workerSharedMemSize = 100 << 20 // 100 MB
|
||||
)
|
||||
|
||||
// worker manages a worker process running a test binary. The worker object
|
||||
|
|
@ -508,7 +512,7 @@ func (ws *workerServer) fuzz(ctx context.Context, args fuzzArgs) fuzzResponse {
|
|||
// real heuristic once we have one.
|
||||
return fuzzResponse{Interesting: true}
|
||||
default:
|
||||
vals = ws.m.mutate(vals, cap(mem.valueRef()))
|
||||
ws.m.mutate(vals, cap(mem.valueRef()))
|
||||
writeToMem(vals, mem)
|
||||
if err := ws.fuzzFn(CorpusEntry{Values: vals}); err != nil {
|
||||
if minErr := ws.minimize(ctx, vals, mem); minErr != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue