]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.fuzz] internal/fuzz: reduce allocation in the mutator
authorJay Conrod <jayconrod@google.com>
Thu, 1 Apr 2021 21:51:18 +0000 (17:51 -0400)
committerJay Conrod <jayconrod@google.com>
Mon, 5 Apr 2021 17:04:08 +0000 (17:04 +0000)
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>
src/internal/fuzz/fuzz.go
src/internal/fuzz/mutator.go
src/internal/fuzz/mutator_test.go [new file with mode: 0644]
src/internal/fuzz/worker.go

index 9ae1eadaecede2252d11a7919a3a2b93367aa10f..8e0425c0c425d81e12835dbf35b5665b12760964 100644 (file)
@@ -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
                }
index 584c21e8ae2148f4df4b599ed1fb01ebf106330d..aa7297214724bb7f0d3f7b1c9d7b526b2ac8a59c 100644 (file)
@@ -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]))
        }
diff --git a/src/internal/fuzz/mutator_test.go b/src/internal/fuzz/mutator_test.go
new file mode 100644 (file)
index 0000000..b1b5311
--- /dev/null
@@ -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)
+                       }
+               })
+       }
+}
index 3fe5aebbf47b1b5c6e26bdfa06ce3cb2ddbec278..243a12baefe4f33833cd0c89b3946892ee3e7308 100644 (file)
@@ -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 {