// Copyright 2015, Joe Tsai. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE.md file. package main import ( "bufio" "bytes" "fmt" "io" "io/ioutil" "path/filepath" "runtime" "strings" "testing" "github.com/dsnet/compress/internal/testutil" ) type Result struct { R float64 // Rate (MB/s) or ratio (rawSize/compSize) D float64 // Delta ratio relative to primary benchmark } // BenchmarkEncoder benchmarks a single encoder on the given input data using // the selected compression level and reports the result. func BenchmarkEncoder(input []byte, enc Encoder, lvl int) testing.BenchmarkResult { return testing.Benchmark(func(b *testing.B) { b.StopTimer() if enc == nil { b.Fatalf("unexpected error: nil Encoder") } runtime.GC() b.StartTimer() for i := 0; i < b.N; i++ { wr := enc(ioutil.Discard, lvl) _, err := io.Copy(wr, bytes.NewBuffer(input)) if err := wr.Close(); err != nil { b.Fatalf("unexpected error: %v", err) } if err != nil { b.Fatalf("unexpected error: %v", err) } b.SetBytes(int64(len(input))) } }) } // BenchmarkEncoderSuite runs multiple benchmarks across all encoder // implementations, files, levels, and sizes. // // The values returned have the following structure: // results: [len(files)*len(levels)*len(sizes)][len(encs)]Result // names: [len(files)*len(levels)*len(sizes)]string func BenchmarkEncoderSuite(ft Format, encs []string, files []file, levels, sizes []int, tick func()) (results [][]Result, names []string) { return benchmarkSuite(encs, files, levels, sizes, tick, func(input []byte, enc string, lvl int) Result { result := BenchmarkEncoder(input, encoders[ft][enc], lvl) if result.N == 0 { return Result{} } us := (float64(result.T.Nanoseconds()) / 1e3) / float64(result.N) rate := float64(result.Bytes) / us return Result{R: rate} }) } // BenchmarkDecoder benchmarks a single decoder on the given pre-compressed // input data and reports the result. func BenchmarkDecoder(input []byte, dec Decoder) testing.BenchmarkResult { return testing.Benchmark(func(b *testing.B) { b.StopTimer() if dec == nil { b.Fatalf("unexpected error: nil Decoder") } runtime.GC() b.StartTimer() for i := 0; i < b.N; i++ { rd := dec(bufio.NewReader(bytes.NewBuffer(input))) cnt, err := io.Copy(ioutil.Discard, rd) if err := rd.Close(); err != nil { b.Fatalf("unexpected error: %v", err) } if err != nil { b.Fatalf("unexpected error: %v", err) } b.SetBytes(cnt) } }) } // BenchmarkDecoderSuite runs multiple benchmarks across all decoder // implementations, files, levels, and sizes. // // The values returned have the following structure: // results: [len(files)*len(levels)*len(sizes)][len(decs)]Result // names: [len(files)*len(levels)*len(sizes)]string func BenchmarkDecoderSuite(ft Format, decs []string, files []file, levels, sizes []int, ref Encoder, tick func()) (results [][]Result, names []string) { return benchmarkSuite(decs, files, levels, sizes, tick, func(input []byte, dec string, lvl int) Result { buf := new(bytes.Buffer) wr := ref(buf, lvl) if _, err := io.Copy(wr, bytes.NewReader(input)); err != nil { return Result{} } if wr.Close() != nil { return Result{} } output := buf.Bytes() result := BenchmarkDecoder(output, decoders[ft][dec]) if result.N == 0 { return Result{} } us := (float64(result.T.Nanoseconds()) / 1e3) / float64(result.N) rate := float64(result.Bytes) / us return Result{R: rate} }) } // BenchmarkRatioSuite runs multiple benchmarks across all encoder // implementations, files, levels, and sizes. // // The values returned have the following structure: // results: [len(files)*len(levels)*len(sizes)][len(encs)]Result // names: [len(files)*len(levels)*len(sizes)]string func BenchmarkRatioSuite(ft Format, encs []string, files []file, levels, sizes []int, tick func()) (results [][]Result, names []string) { return benchmarkSuite(encs, files, levels, sizes, tick, func(input []byte, enc string, lvl int) Result { buf := new(bytes.Buffer) wr := encoders[ft][enc](buf, lvl) if _, err := io.Copy(wr, bytes.NewReader(input)); err != nil { return Result{} } if wr.Close() != nil { return Result{} } output := buf.Bytes() ratio := float64(len(input)) / float64(len(output)) return Result{R: ratio} }) } type benchFunc func(input []byte, codec string, level int) Result func benchmarkSuite(codecs []string, files []file, levels, sizes []int, tick func(), run benchFunc) ([][]Result, []string) { // Allocate buffers for the result. d0 := len(files) * len(levels) * len(sizes) d1 := len(codecs) results := make([][]Result, d0) for i := range results { results[i] = make([]Result, d1) } names := make([]string, d0) // Run the benchmark for every codec, file, level, and size. var i int for _, f := range files { for _, l := range levels { for _, n := range sizes { b, err := ioutil.ReadFile(f.Abs) if err == nil { b = testutil.ResizeData(b, n) } fname := strings.Replace(f.Rel, string(filepath.Separator), "_", -1) name := fmt.Sprintf("%s:%d:%s", fname, l, intName(int64(len(b)))) for j, c := range codecs { if tick != nil { tick() } names[i] = name if err == nil { results[i][j] = run(b, c, l) } results[i][j].D = results[i][j].R / results[i][0].R } i++ } } } return results, names }