From 4230659178408bf47cae30daf5a82f4214b46351 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Sun, 17 Mar 2024 11:33:29 +0100 Subject: [PATCH] initial commit --- .gitignore | 3 ++ README.md | 25 ++++++++++++++++ bench_test.go | 51 ++++++++++++++++++++++++++++++++ generate/main.go | 50 +++++++++++++++++++++++++++++++ go.mod | 8 +++++ go.sum | 12 ++++++++ plot/main.go | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 226 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bench_test.go create mode 100644 generate/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 plot/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..31370fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +data +plot.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1f58ec --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# fat contexts + +This repo holds code for [On context-induced performance bottleneck in Go](https://gabnotes.org/fat-contexts/). + +Reproduce my results: + +```console +$ go test -bench=. +goos: darwin +goarch: arm64 +pkg: trash +BenchmarkContext/shadow_1000-8 19352 61271 ns/op +BenchmarkContext/fat_1000-8 532 2187010 ns/op +BenchmarkContext/shadow_10000-8 1903 639371 ns/op +BenchmarkContext/fat_10000-8 5 219400100 ns/op +BenchmarkContext/shadow_100000-8 194 6374344 ns/op +BenchmarkContext/fat_100000-8 1 21851940167 ns/op +PASS +ok trash 30.801s +$ go run ./generate/main.go > data +$ go run ./plot/main.go +$ open plot.html +``` + +Sorry there are a lot of hardcoded file names in here, but since I don't intend on reusing this code I won't fix it. diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 0000000..92f8a6d --- /dev/null +++ b/bench_test.go @@ -0,0 +1,51 @@ +package main_test + +import ( + "context" + "fmt" + "math/rand/v2" + "testing" +) + +const key = "key" + +func BenchmarkContext(b *testing.B) { + benchmarks := []struct { + times uint64 + }{ + {1_000}, + {10_000}, + {100_000}, + } + for _, bm := range benchmarks { + ctx := context.WithValue(context.Background(), key, "some value") + b.Run(fmt.Sprintf("shadow %v", bm.times), func(b *testing.B) { + for i := 0; i < b.N; i++ { + shadow(ctx, bm.times) + } + }) + b.Run(fmt.Sprintf("fat %v", bm.times), func(b *testing.B) { + for i := 0; i < b.N; i++ { + fat(ctx, bm.times) + } + }) + } +} + +func shadow(ctx context.Context, times uint64) { + for range times { + ctx := contextWithRandom(ctx) + _ = ctx.Value(key) + } +} + +func fat(ctx context.Context, times uint64) { + for range times { + ctx = contextWithRandom(ctx) + _ = ctx.Value(key) + } +} + +func contextWithRandom(ctx context.Context) context.Context { + return context.WithValue(ctx, "other_key", rand.Int64()) +} diff --git a/generate/main.go b/generate/main.go new file mode 100644 index 0000000..43dfa10 --- /dev/null +++ b/generate/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "fmt" + "time" + + "github.com/gofrs/uuid" +) + +const key = "key" + +func main() { + const times = 10_000 + + // Setup the value we want to retrieve in each iteration + ctx := context.WithValue(context.Background(), key, "some-val") + + fat(ctx, times) + shadow(ctx, times) +} + +func fat(ctx context.Context, times uint64) { + for range times { + // wrap the context, each iteration makes it bigger + ctx = contextWithRandom(ctx) + + start := time.Now() + // simulate the logging lib which retrieves context values + _ = ctx.Value(key) + + fmt.Printf("fat,%v\n", time.Since(start).Nanoseconds()) + } +} + +func shadow(ctx context.Context, times uint64) { + for range times { + // shadow the context, each iteration creates a new one and it doesn't grow + ctx := contextWithRandom(ctx) + + start := time.Now() + _ = ctx.Value(key) + + fmt.Printf("shadow,%v\n", time.Since(start).Nanoseconds()) + } +} + +func contextWithRandom(ctx context.Context) context.Context { + return context.WithValue(ctx, "other_key", uuid.Must(uuid.NewV4())) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9c5d3fc --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module trash + +go 1.22.0 + +require ( + github.com/go-echarts/go-echarts/v2 v2.3.3 + github.com/gofrs/uuid v4.4.0+incompatible +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1650ddd --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-echarts/go-echarts/v2 v2.3.3 h1:uImZAk6qLkC6F9ju6mZ5SPBqTyK8xjZKwSmwnCg4bxg= +github.com/go-echarts/go-echarts/v2 v2.3.3/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= +github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/plot/main.go b/plot/main.go new file mode 100644 index 0000000..9487eb8 --- /dev/null +++ b/plot/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "encoding/csv" + "github.com/go-echarts/go-echarts/v2/charts" + "github.com/go-echarts/go-echarts/v2/opts" + "io" + "log" + "os" + "strconv" +) + +func main() { + shadow, fat := getData() + plot := charts.NewLine() + xAxis := make([]int, len(shadow)) + for i := range xAxis { + xAxis[i] = i + } + + plot.SetGlobalOptions( + charts.WithXAxisOpts(opts.XAxis{Name: "loop count"}), + charts.WithYAxisOpts(opts.YAxis{Name: "Nanosecond"}), + ) + + plot. + SetXAxis(xAxis). + AddSeries("Shadow", shadow). + AddSeries("Fat", fat) + + f, err := os.Create("plot.html") + if err != nil { + panic(err) + } + err = plot.Render(f) + if err != nil { + panic(err) + } +} + +func getData() ([]opts.LineData, []opts.LineData) { + var shadow []opts.LineData + var fat []opts.LineData + + f, err := os.Open("data") + if err != nil { + panic(err) + } + + r := csv.NewReader(f) + for { + record, err := r.Read() + if err == io.EOF { + break + } + if err != nil { + log.Fatal(err) + } + + series := record[0] + val, err := strconv.ParseUint(record[1], 10, 64) + if err != nil { + log.Fatal(err) + } + + point := opts.LineData{Value: val, Name: "ns"} + + switch series { + case "shadow": + shadow = append(shadow, point) + case "fat": + fat = append(fat, point) + } + } + + return shadow, fat +}