Skip to content

Commit f4b625e

Browse files
RaduBerindeianlancetaylor
authored andcommitted
trace: don't allocate all events upfront
SetMaxEvent allocates storage for all events, which is very expensive if we want to set a high value; even with small values, the unnecessary allocation of the initial slice is measurable. We improve the performance by having a few events preallocated as part of the trace, and appending as necessary. We also co-locate the flags in event which makes it smaller (by a word). We also add a set of benchmarks; before and after amortized cost per-event is shown: name old time/op new time/op delta Trace_Default_2-32 1.19µs ± 1% 0.96µs ± 0% -18.94% Trace_Default_10-32 579ns ± 0% 635ns ± 1% +9.82% Trace_Default_100-32 463ns ± 1% 466ns ± 1% ~ Trace_Default_1000-32 451ns ± 1% 451ns ± 0% ~ Trace_Default_10000-32 451ns ± 2% 449ns ± 1% ~ Trace_10_2-32 1.41µs ± 1% 0.96µs ± 1% -31.74% Trace_10_10-32 623ns ± 1% 634ns ± 1% +1.73% Trace_10_100-32 469ns ± 1% 466ns ± 1% ~ Trace_10_1000-32 452ns ± 1% 453ns ± 2% ~ Trace_10_10000-32 451ns ± 1% 449ns ± 1% ~ Trace_100_2-32 2.78µs ± 2% 0.97µs ± 1% -65.28% Trace_100_10-32 912ns ± 1% 637ns ± 1% -30.23% Trace_100_100-32 479ns ± 1% 541ns ± 0% +12.77% Trace_100_1000-32 510ns ± 0% 541ns ± 1% +6.08% Trace_100_10000-32 514ns ± 1% 540ns ± 0% +5.14% Trace_1000_2-32 17.2µs ± 1% 1.0µs ± 1% -94.39% Trace_1000_10-32 3.90µs ± 1% 0.64µs ± 0% -83.68% Trace_1000_100-32 814ns ± 1% 542ns ± 0% -33.46% Trace_1000_1000-32 503ns ± 1% 581ns ± 0% +15.56% Trace_1000_10000-32 1.28µs ± 2% 1.03µs ± 1% -19.68% Change-Id: I5bbc12153a9f30dff821ef139583cb1c07a11069 Reviewed-on: https://go-review.googlesource.com/30615 Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
1 parent 9b4f494 commit f4b625e

File tree

2 files changed

+109
-8
lines changed

2 files changed

+109
-8
lines changed

trace/trace.go

+16-8
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,8 @@ func New(family, title string) Trace {
333333
tr.ref()
334334
tr.Family, tr.Title = family, title
335335
tr.Start = time.Now()
336-
tr.events = make([]event, 0, maxEventsPerTrace)
336+
tr.maxEvents = maxEventsPerTrace
337+
tr.events = tr.eventsBuf[:0]
337338

338339
activeMu.RLock()
339340
s := activeTraces[tr.Family]
@@ -650,8 +651,8 @@ type event struct {
650651
Elapsed time.Duration // since previous event in trace
651652
NewDay bool // whether this event is on a different day to the previous event
652653
Recyclable bool // whether this event was passed via LazyLog
653-
What interface{} // string or fmt.Stringer
654654
Sensitive bool // whether this event contains sensitive information
655+
What interface{} // string or fmt.Stringer
655656
}
656657

657658
// WhenString returns a string representation of the elapsed time of the event.
@@ -692,14 +693,17 @@ type trace struct {
692693
IsError bool
693694

694695
// Append-only sequence of events (modulo discards).
695-
mu sync.RWMutex
696-
events []event
696+
mu sync.RWMutex
697+
events []event
698+
maxEvents int
697699

698700
refs int32 // how many buckets this is in
699701
recycler func(interface{})
700702
disc discarded // scratch space to avoid allocation
701703

702704
finishStack []byte // where finish was called, if DebugUseAfterFinish is set
705+
706+
eventsBuf [4]event // preallocated buffer in case we only log a few events
703707
}
704708

705709
func (tr *trace) reset() {
@@ -711,11 +715,15 @@ func (tr *trace) reset() {
711715
tr.traceID = 0
712716
tr.spanID = 0
713717
tr.IsError = false
718+
tr.maxEvents = 0
714719
tr.events = nil
715720
tr.refs = 0
716721
tr.recycler = nil
717722
tr.disc = 0
718723
tr.finishStack = nil
724+
for i := range tr.eventsBuf {
725+
tr.eventsBuf[i] = event{}
726+
}
719727
}
720728

721729
// delta returns the elapsed time since the last event or the trace start,
@@ -753,11 +761,11 @@ func (tr *trace) addEvent(x interface{}, recyclable, sensitive bool) {
753761
e := event{When: time.Now(), What: x, Recyclable: recyclable, Sensitive: sensitive}
754762
tr.mu.Lock()
755763
e.Elapsed, e.NewDay = tr.delta(e.When)
756-
if len(tr.events) < cap(tr.events) {
764+
if len(tr.events) < tr.maxEvents {
757765
tr.events = append(tr.events, e)
758766
} else {
759767
// Discard the middle events.
760-
di := int((cap(tr.events) - 1) / 2)
768+
di := int((tr.maxEvents - 1) / 2)
761769
if d, ok := tr.events[di].What.(*discarded); ok {
762770
(*d)++
763771
} else {
@@ -777,7 +785,7 @@ func (tr *trace) addEvent(x interface{}, recyclable, sensitive bool) {
777785
go tr.recycler(tr.events[di+1].What)
778786
}
779787
copy(tr.events[di+1:], tr.events[di+2:])
780-
tr.events[cap(tr.events)-1] = e
788+
tr.events[tr.maxEvents-1] = e
781789
}
782790
tr.mu.Unlock()
783791
}
@@ -803,7 +811,7 @@ func (tr *trace) SetTraceInfo(traceID, spanID uint64) {
803811
func (tr *trace) SetMaxEvents(m int) {
804812
// Always keep at least three events: first, discarded count, last.
805813
if len(tr.events) == 0 && m > 3 {
806-
tr.events = make([]event, 0, m)
814+
tr.maxEvents = m
807815
}
808816
}
809817

trace/trace_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,96 @@ func TestAuthRequest(t *testing.T) {
6969
}
7070
}
7171
}
72+
73+
func benchmarkTrace(b *testing.B, maxEvents, numEvents int) {
74+
numSpans := (b.N + numEvents + 1) / numEvents
75+
76+
for i := 0; i < numSpans; i++ {
77+
tr := New("test", "test")
78+
tr.SetMaxEvents(maxEvents)
79+
for j := 0; j < numEvents; j++ {
80+
tr.LazyPrintf("%d", j)
81+
}
82+
tr.Finish()
83+
}
84+
}
85+
86+
func BenchmarkTrace_Default_2(b *testing.B) {
87+
benchmarkTrace(b, 0, 2)
88+
}
89+
90+
func BenchmarkTrace_Default_10(b *testing.B) {
91+
benchmarkTrace(b, 0, 10)
92+
}
93+
94+
func BenchmarkTrace_Default_100(b *testing.B) {
95+
benchmarkTrace(b, 0, 100)
96+
}
97+
98+
func BenchmarkTrace_Default_1000(b *testing.B) {
99+
benchmarkTrace(b, 0, 1000)
100+
}
101+
102+
func BenchmarkTrace_Default_10000(b *testing.B) {
103+
benchmarkTrace(b, 0, 10000)
104+
}
105+
106+
func BenchmarkTrace_10_2(b *testing.B) {
107+
benchmarkTrace(b, 10, 2)
108+
}
109+
110+
func BenchmarkTrace_10_10(b *testing.B) {
111+
benchmarkTrace(b, 10, 10)
112+
}
113+
114+
func BenchmarkTrace_10_100(b *testing.B) {
115+
benchmarkTrace(b, 10, 100)
116+
}
117+
118+
func BenchmarkTrace_10_1000(b *testing.B) {
119+
benchmarkTrace(b, 10, 1000)
120+
}
121+
122+
func BenchmarkTrace_10_10000(b *testing.B) {
123+
benchmarkTrace(b, 10, 10000)
124+
}
125+
126+
func BenchmarkTrace_100_2(b *testing.B) {
127+
benchmarkTrace(b, 100, 2)
128+
}
129+
130+
func BenchmarkTrace_100_10(b *testing.B) {
131+
benchmarkTrace(b, 100, 10)
132+
}
133+
134+
func BenchmarkTrace_100_100(b *testing.B) {
135+
benchmarkTrace(b, 100, 100)
136+
}
137+
138+
func BenchmarkTrace_100_1000(b *testing.B) {
139+
benchmarkTrace(b, 100, 1000)
140+
}
141+
142+
func BenchmarkTrace_100_10000(b *testing.B) {
143+
benchmarkTrace(b, 100, 10000)
144+
}
145+
146+
func BenchmarkTrace_1000_2(b *testing.B) {
147+
benchmarkTrace(b, 1000, 2)
148+
}
149+
150+
func BenchmarkTrace_1000_10(b *testing.B) {
151+
benchmarkTrace(b, 1000, 10)
152+
}
153+
154+
func BenchmarkTrace_1000_100(b *testing.B) {
155+
benchmarkTrace(b, 1000, 100)
156+
}
157+
158+
func BenchmarkTrace_1000_1000(b *testing.B) {
159+
benchmarkTrace(b, 1000, 1000)
160+
}
161+
162+
func BenchmarkTrace_1000_10000(b *testing.B) {
163+
benchmarkTrace(b, 1000, 10000)
164+
}

0 commit comments

Comments
 (0)