Skip to content

Commit 7c0529b

Browse files
committed
zap.Any add benchmarks
This is a prefactor for #1301, #1302, #1304, #1305, #1307, #1308 and #1310. We're writing various approaches to reduce the stock size and it's painful to keep copy-pasting the tests between PRs. This was suggested in @prashantv in #1307. The tests are mostly based on tests in #1303, but made "more generic", as #1307 we might want to test across more than just a single type. It does make the tests a bit harder to setup. Some of the setup is inconvenient (we duplicate the value in both `typed` and `any` version of the tests) but hopefully okay to understand. A fully non-duplicated alternative would likely require something like #1310 itself. For #1307 in particular a test against interface type would likely be needed, so adding it here too. The tests compare two code paths, with the same arguments, one using a strongly typed method and second using `zap.Any`. We have: - a simple "create field" case for a baseline - a "create and log" case for a realistic case (we typically log the fields) - a "create and log in a goroutine" case for the pathological case we're trying to solve for. - a "create and long in goroutine in a pre-warmed system" that does the above, but before tries to affect the starting goroutine stack size to provide an realistic example. Without this, for any tests with 2+ goroutines, the cost of `zap.Any` is not visible, as we always end up expanding the stack even in the strongly typed methods. The test results are: ``` goos: darwin goarch: arm64 pkg: go.uber.org/zap BenchmarkAny/string-typ-no-logger 166879518 6.988 ns/op 0 B/op 0 allocs/op BenchmarkAny/string-typ-no-logger-12 167398297 6.973 ns/op 0 B/op 0 allocs/op BenchmarkAny/string-any-no-logger 87669631 13.97 ns/op 0 B/op 0 allocs/op BenchmarkAny/string-any-no-logger-12 86760837 14.11 ns/op 0 B/op 0 allocs/op BenchmarkAny/string-typ-logger 3059485 395.5 ns/op 64 B/op 1 allocs/op BenchmarkAny/string-typ-logger-12 3141176 379.7 ns/op 64 B/op 1 allocs/op BenchmarkAny/string-any-logger 2995699 401.3 ns/op 64 B/op 1 allocs/op BenchmarkAny/string-any-logger-12 3071046 391.1 ns/op 64 B/op 1 allocs/op BenchmarkAny/string-typ-logger-go 784323 1351 ns/op 146 B/op 2 allocs/op BenchmarkAny/string-typ-logger-go-12 2000835 613.9 ns/op 96 B/op 2 allocs/op BenchmarkAny/string-any-logger-go 477486 2479 ns/op 117 B/op 2 allocs/op BenchmarkAny/string-any-logger-go-12 1830955 680.0 ns/op 112 B/op 2 allocs/op BenchmarkAny/string-typ-logger-go-stack 841566 1328 ns/op 96 B/op 2 allocs/op BenchmarkAny/string-typ-logger-go-stack-12 2625226 479.6 ns/op 96 B/op 2 allocs/op BenchmarkAny/string-any-logger-go-stack 486084 2493 ns/op 112 B/op 2 allocs/op BenchmarkAny/string-any-logger-go-stack-12 2658640 667.9 ns/op 112 B/op 2 allocs/op BenchmarkAny/stringer-typ-no-logger 147314238 8.034 ns/op 0 B/op 0 allocs/op BenchmarkAny/stringer-typ-no-logger-12 157857937 7.436 ns/op 0 B/op 0 allocs/op BenchmarkAny/stringer-any-no-logger 58872349 20.19 ns/op 0 B/op 0 allocs/op BenchmarkAny/stringer-any-no-logger-12 60532305 20.27 ns/op 0 B/op 0 allocs/op BenchmarkAny/stringer-typ-logger 3094204 411.7 ns/op 64 B/op 1 allocs/op BenchmarkAny/stringer-typ-logger-12 3163489 383.7 ns/op 64 B/op 1 allocs/op BenchmarkAny/stringer-any-logger 2981940 427.2 ns/op 64 B/op 1 allocs/op BenchmarkAny/stringer-any-logger-12 2777792 394.0 ns/op 64 B/op 1 allocs/op BenchmarkAny/stringer-typ-logger-go 911761 1335 ns/op 96 B/op 2 allocs/op BenchmarkAny/stringer-typ-logger-go-12 2006440 605.2 ns/op 96 B/op 2 allocs/op BenchmarkAny/stringer-any-logger-go 467934 2518 ns/op 112 B/op 2 allocs/op BenchmarkAny/stringer-any-logger-go-12 1786076 683.1 ns/op 112 B/op 2 allocs/op BenchmarkAny/stringer-typ-logger-go-stack 855794 1316 ns/op 96 B/op 2 allocs/op BenchmarkAny/stringer-typ-logger-go-stack-12 2598783 434.5 ns/op 96 B/op 2 allocs/op BenchmarkAny/stringer-any-logger-go-stack 473282 2474 ns/op 112 B/op 2 allocs/op BenchmarkAny/stringer-any-logger-go-stack-12 2020183 651.9 ns/op 112 B/op 2 allocs/op PASS ok go.uber.org/zap 53.516s ```
1 parent 8db347b commit 7c0529b

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed

logger_bench_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ package zap
2222

2323
import (
2424
"errors"
25+
"runtime"
26+
"sync"
2527
"testing"
2628
"time"
2729

@@ -238,3 +240,130 @@ func Benchmark100Fields(b *testing.B) {
238240
logger.With(first...).Info("Child loggers with lots of context.", second...)
239241
}
240242
}
243+
244+
func dummy(wg *sync.WaitGroup, s string, i int) string {
245+
if i == 0 {
246+
wg.Wait()
247+
return "1" + s
248+
}
249+
return dummy(wg, s, i-1)
250+
}
251+
252+
func stackGrower(n int) *sync.WaitGroup {
253+
wg := sync.WaitGroup{}
254+
wg.Add(1)
255+
256+
go dummy(&wg, "hi", n)
257+
return &wg
258+
}
259+
260+
func BenchmarkAny(b *testing.B) {
261+
var (
262+
logger = New(
263+
zapcore.NewCore(
264+
zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
265+
&ztest.Discarder{},
266+
DebugLevel,
267+
),
268+
)
269+
key = "some-long-string-longer-than-16"
270+
)
271+
272+
tests := []struct {
273+
name string
274+
typed func() Field
275+
anyArg any
276+
}{
277+
{
278+
name: "string",
279+
typed: func() Field { return String(key, "yet-another-long-string") },
280+
anyArg: "yet-another-long-string",
281+
},
282+
{
283+
name: "stringer",
284+
typed: func() Field { return Stringer(key, InfoLevel) },
285+
anyArg: InfoLevel,
286+
},
287+
}
288+
289+
for _, tt := range tests {
290+
b.Run(tt.name+"-typ-no-logger", func(b *testing.B) {
291+
for i := 0; i < b.N; i++ {
292+
f := tt.typed()
293+
runtime.KeepAlive(f)
294+
}
295+
})
296+
b.Run(tt.name+"-any-no-logger", func(b *testing.B) {
297+
for i := 0; i < b.N; i++ {
298+
f := Any(key, tt.anyArg)
299+
runtime.KeepAlive(f)
300+
}
301+
})
302+
b.Run(tt.name+"-typ-logger", func(b *testing.B) {
303+
for i := 0; i < b.N; i++ {
304+
logger.Info("", tt.typed())
305+
}
306+
})
307+
b.Run(tt.name+"-any-logger", func(b *testing.B) {
308+
for i := 0; i < b.N; i++ {
309+
logger.Info("", Any(key, tt.anyArg))
310+
}
311+
})
312+
b.Run(tt.name+"-typ-logger-go", func(b *testing.B) {
313+
wg := sync.WaitGroup{}
314+
wg.Add(b.N)
315+
b.ResetTimer()
316+
for i := 0; i < b.N; i++ {
317+
go func() {
318+
logger.Info("", tt.typed())
319+
wg.Done()
320+
}()
321+
}
322+
wg.Wait()
323+
})
324+
b.Run(tt.name+"-any-logger-go", func(b *testing.B) {
325+
wg := sync.WaitGroup{}
326+
wg.Add(b.N)
327+
b.ResetTimer()
328+
for i := 0; i < b.N; i++ {
329+
go func() {
330+
logger.Info("", Any(key, tt.anyArg))
331+
wg.Done()
332+
}()
333+
}
334+
wg.Wait()
335+
})
336+
// The stack growing below simulates production setup where some other
337+
// goroutines exist and affect the starting goroutine stack size up.
338+
// Otherwise, for tests with 2+ goroutines, the cost of starting the goroutine
339+
// dominates and the cost of `any` stack overallocation is not visible.
340+
b.Run(tt.name+"-typ-logger-go-stack", func(b *testing.B) {
341+
wg := sync.WaitGroup{}
342+
wg.Add(b.N)
343+
defer stackGrower(2000).Done()
344+
b.ResetTimer()
345+
for i := 0; i < b.N; i++ {
346+
go func() {
347+
logger.Info("", tt.typed())
348+
wg.Done()
349+
}()
350+
}
351+
wg.Wait()
352+
b.StopTimer()
353+
})
354+
b.Run(tt.name+"-any-logger-go-stack", func(b *testing.B) {
355+
wg := sync.WaitGroup{}
356+
wg.Add(b.N)
357+
defer stackGrower(2000).Done()
358+
b.ResetTimer()
359+
for i := 0; i < b.N; i++ {
360+
go func() {
361+
logger.Info("", Any(key, tt.anyArg))
362+
wg.Done()
363+
}()
364+
}
365+
wg.Wait()
366+
b.StopTimer()
367+
})
368+
}
369+
}

0 commit comments

Comments
 (0)