initial commit

This commit is contained in:
Gabriel Augendre 2024-03-17 11:33:29 +01:00
commit 4230659178
7 changed files with 226 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.idea
data
plot.html

25
README.md Normal file
View file

@ -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.

51
bench_test.go Normal file
View file

@ -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())
}

50
generate/main.go Normal file
View file

@ -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()))
}

8
go.mod Normal file
View file

@ -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
)

12
go.sum Normal file
View file

@ -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=

77
plot/main.go Normal file
View file

@ -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
}