Created
February 5, 2026 22:57
-
-
Save alexrios/2c4d118695c4e24da36c3136ef334018 to your computer and use it in GitHub Desktop.
Green Tea GC benchmarking examples — companion code for https://alexrios.me/blog/greentea-gc-series-benchmarking
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Package main provides benchmark examples for verifying Green Tea GC blog post code. | |
| // These benchmarks demonstrate proper Go benchmarking patterns. | |
| package main | |
| import ( | |
| "runtime" | |
| "runtime/metrics" | |
| "testing" | |
| ) | |
| // Sample data structure to allocate | |
| type SmallObject struct { | |
| ID int64 | |
| Name string | |
| Value float64 | |
| Tags [4]string | |
| } | |
| // BenchmarkAllocation demonstrates allocation-heavy workload | |
| // that would benefit from Green Tea GC optimization. | |
| func BenchmarkAllocation(b *testing.B) { | |
| for b.Loop() { | |
| // Allocate small objects (< 512 bytes) - Green Tea's target | |
| obj := &SmallObject{ | |
| ID: 42, | |
| Name: "test", | |
| Value: 3.14, | |
| Tags: [4]string{"a", "b", "c", "d"}, | |
| } | |
| _ = obj | |
| } | |
| } | |
| // BenchmarkAllocationOldStyle shows the pre-Go 1.24 pattern for comparison. | |
| // The b.Loop() pattern above is preferred in Go 1.24+. | |
| func BenchmarkAllocationOldStyle(b *testing.B) { | |
| for i := 0; i < b.N; i++ { | |
| obj := &SmallObject{ | |
| ID: int64(i), | |
| Name: "test", | |
| Value: 3.14, | |
| Tags: [4]string{"a", "b", "c", "d"}, | |
| } | |
| _ = obj | |
| } | |
| } | |
| // BenchmarkSingleAllocation shows that runtime.KeepAlive alone doesn't force heap allocation. | |
| // Escape analysis still keeps the object on the stack because the pointer never actually escapes. | |
| func BenchmarkSingleAllocation(b *testing.B) { | |
| b.ReportAllocs() | |
| for b.Loop() { | |
| obj := &SmallObject{ | |
| ID: 42, | |
| Name: "test", | |
| Value: 3.14, | |
| Tags: [4]string{"a", "b", "c", "d"}, | |
| } | |
| runtime.KeepAlive(obj) | |
| } | |
| } | |
| // BenchmarkWithGCPressure creates enough allocations to trigger GC. | |
| func BenchmarkWithGCPressure(b *testing.B) { | |
| b.ReportAllocs() | |
| for b.Loop() { | |
| // Allocate a slice of small objects | |
| objects := make([]*SmallObject, 1000) | |
| for j := 0; j < 1000; j++ { | |
| objects[j] = &SmallObject{ | |
| ID: int64(j), | |
| Name: "pressure-test", | |
| Value: float64(j) * 1.5, | |
| } | |
| } | |
| // Force objects to escape | |
| runtime.KeepAlive(objects) | |
| } | |
| } | |
| // BenchmarkLargeObjects shows workload that won't benefit from Green Tea. | |
| // Green Tea only optimizes objects <= 512 bytes. | |
| func BenchmarkLargeObjects(b *testing.B) { | |
| b.ReportAllocs() | |
| for b.Loop() { | |
| // Large allocation - won't benefit from Green Tea | |
| data := make([]byte, 8192) | |
| data[0] = 1 | |
| runtime.KeepAlive(data) | |
| } | |
| } | |
| // TestReadGCMetrics demonstrates the runtime/metrics API usage from the blog post. | |
| func TestReadGCMetrics(t *testing.T) { | |
| // Read GC metrics - this is the exact code from the blog post | |
| samples := []metrics.Sample{ | |
| {Name: "/gc/heap/allocs:bytes"}, | |
| {Name: "/gc/heap/goal:bytes"}, | |
| {Name: "/gc/scan/heap:bytes"}, | |
| {Name: "/sched/pauses/stopping/gc:seconds"}, | |
| } | |
| metrics.Read(samples) | |
| // The /sched/pauses/stopping/gc:seconds returns a histogram | |
| // showing STW (Stop The World) pause distribution | |
| for _, s := range samples { | |
| switch s.Value.Kind() { | |
| case metrics.KindUint64: | |
| println(s.Name, "=", s.Value.Uint64()) | |
| case metrics.KindFloat64: | |
| println(s.Name, "=", s.Value.Float64()) | |
| case metrics.KindFloat64Histogram: | |
| println(s.Name, "= <histogram>") | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment