From a55fb71faf1a05363fc1aa58ffeb21f233bfacf0 Mon Sep 17 00:00:00 2001 From: John Howard Date: Tue, 14 Sep 2021 12:54:44 -0700 Subject: [PATCH] Optimize `Record()` to avoid extra allocations (#1267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Register metrics in benchmark Currently, the metric we record is not registered. This hits the fast-path code of not actually recording the metric, so we miss out on detecting any performance to that main code path. This registers the metrics so we actually trigger `record`. * Optimize Record() to avoid extra allocations Currently, `Record()` re-uses code with `RecordWithOptions`. This always creates allocations for createRecordOption, which is not needed in this case - we only have measurements and not generic options. With a little code duplication, we can reduce these allocations. ``` name old time/op new time/op delta Record0-6 92.2ns ± 9% 1.7ns ± 4% -98.11% (p=0.008 n=5+5) Record1-6 665ns ± 5% 634ns ± 6% -4.57% (p=0.095 n=5+5) Record8-6 1.24µs ± 5% 1.21µs ± 5% -2.18% (p=0.206 n=5+5) Record8_WithRecorder-6 796ns ± 5% 777ns ± 5% -2.45% (p=0.222 n=5+5) Record8_Parallel-6 1.21µs ± 2% 1.26µs ±24% ~ (p=0.690 n=5+5) Record8_8Tags-6 1.23µs ± 4% 1.23µs ± 2% ~ (p=0.968 n=5+5) name old alloc/op new alloc/op delta Record0-6 80.0B ± 0% 0.0B -100.00% (p=0.008 n=5+5) Record1-6 200B ± 0% 120B ± 0% -40.00% (p=0.008 n=5+5) Record8-6 424B ± 0% 344B ± 0% -18.87% (p=0.008 n=5+5) Record8_WithRecorder-6 424B ± 0% 424B ± 0% ~ (all equal) Record8_Parallel-6 424B ± 0% 344B ± 0% -18.87% (p=0.008 n=5+5) Record8_8Tags-6 424B ± 0% 344B ± 0% -18.87% (p=0.008 n=5+5) name old allocs/op new allocs/op delta Record0-6 1.00 ± 0% 0.00 -100.00% (p=0.008 n=5+5) Record1-6 4.00 ± 0% 3.00 ± 0% -25.00% (p=0.008 n=5+5) Record8-6 4.00 ± 0% 3.00 ± 0% -25.00% (p=0.008 n=5+5) Record8_WithRecorder-6 4.00 ± 0% 4.00 ± 0% ~ (all equal) Record8_Parallel-6 4.00 ± 0% 3.00 ± 0% -25.00% (p=0.008 n=5+5) Record8_8Tags-6 4.00 ± 0% 3.00 ± 0% -25.00% (p=0.008 n=5+5) ``` --- stats/benchmark_test.go | 10 +++++++++- stats/record.go | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/stats/benchmark_test.go b/stats/benchmark_test.go index e784b8c4c..fade54f9a 100644 --- a/stats/benchmark_test.go +++ b/stats/benchmark_test.go @@ -109,5 +109,13 @@ func BenchmarkRecord8_8Tags(b *testing.B) { } func makeMeasure() *stats.Int64Measure { - return stats.Int64("m", "test measure", "") + m := stats.Int64("m", "test measure", "") + v := &view.View{ + Measure: m, + Aggregation: view.Sum(), + } + if err := view.Register(v); err != nil { + panic(err.Error()) + } + return m } diff --git a/stats/record.go b/stats/record.go index 2b9728346..f8a52bcd9 100644 --- a/stats/record.go +++ b/stats/record.go @@ -89,7 +89,24 @@ func createRecordOption(ros ...Options) *recordOptions { // Record records one or multiple measurements with the same context at once. // If there are any tags in the context, measurements will be tagged with them. func Record(ctx context.Context, ms ...Measurement) { - RecordWithOptions(ctx, WithMeasurements(ms...)) + // Record behaves the same as RecordWithOptions, but because we do not have to handle generic functionality + // (RecordOptions) we can reduce some allocations to speed up this hot path + if len(ms) == 0 { + return + } + recorder := internal.DefaultRecorder + record := false + for _, m := range ms { + if m.desc.subscribed() { + record = true + break + } + } + if !record { + return + } + recorder(tag.FromContext(ctx), ms, nil) + return } // RecordWithTags records one or multiple measurements at once.