Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ddtrace/tracer: Remove log.Debug from hot path (-2 allocs; -5% CPU) #1186

Merged
merged 4 commits into from Mar 8, 2022

Conversation

evanj
Copy link
Contributor

@evanj evanj commented Feb 28, 2022

The log.Debug function takes a ...interface{} argument which must be
heap allocated. This is done even if debug logging is disabled, which
is the common case. Add a log.DebugEnabled() function to check if it
is enabled, and skip allocating this slice in the common case. The
benchstat output from my laptop shows this saves about 5% CPU and
2 allocs/call for the BenchmarkStartSpan microbenchmark.

Normally, I do not like this sort of ugly log level check. However,
given that StartSpan is widely called, and sometimes from hot code
paths, this may be worthit.

name         old time/op    new time/op    delta
StartSpan-8    1.63µs ± 5%    1.55µs ±10%   -5.00%  (p=0.003 n=9+9)

name         old alloc/op   new alloc/op   delta
StartSpan-8    1.55kB ± 0%    1.52kB ± 0%   -2.08%  (p=0.000 n=10+8)

name         old allocs/op  new allocs/op  delta
StartSpan-8      19.0 ± 0%      17.0 ± 0%  -10.53%  (p=0.000 n=10+10)

The log.Debug function takes a ...interface{} argument which must be
heap allocated. This is done even if debug logging is disabled, which
is the common case. Add a log.DebugEnabled() function to check if it
is enabled, and skip allocating this slice in the common case. The
benchstat output from my laptop shows this saves about 5% CPU and
2 allocs/call for the BenchmarkStartSpan microbenchmark.

Normally, I do not like this sort of ugly log level check. However,
given that StartSpan is widely called, and sometimes from hot code
paths, this may be worthit.

name         old time/op    new time/op    delta
StartSpan-8    1.63µs ± 5%    1.55µs ±10%   -5.00%  (p=0.003 n=9+9)

name         old alloc/op   new alloc/op   delta
StartSpan-8    1.55kB ± 0%    1.52kB ± 0%   -2.08%  (p=0.000 n=10+8)

name         old allocs/op  new allocs/op  delta
StartSpan-8      19.0 ± 0%      17.0 ± 0%  -10.53%  (p=0.000 n=10+10)
@evanj evanj requested a review from a team February 28, 2022 15:10
felixge
felixge previously approved these changes Mar 1, 2022
Copy link
Member

@felixge felixge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM – I played around with this a bit to see if the same result can be achieved by getting the compiler to inline the log.Debug() call. But unfortunately I wasn't able to get the cost of the Debug function below the threshold of 80 (only got it to 122 from 228 by applying my NIT suggestion).

Anyway, since this is all internal and only a single call-site needs the explicit log.DebugEnabled() check, I think this is a fine tradeoff.

mu.RUnlock()
return lvl == LevelDebug
}

// Debug prints the given message if the level is LevelDebug.
func Debug(fmt string, a ...interface{}) {
mu.RLock()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Remove code duplication by calling DebugEnabled() here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops excellent suggestion; fixed

@evanj
Copy link
Contributor Author

evanj commented Mar 1, 2022

I think it is tricky to eliminate the slice allocation, since the arguments eventually get passed to fmt.Printf? I'm not entirely sure if inlining could do this, since it would need to rearrange when the argument slice gets allocated with the check. Uber's zap logging library provides a very clever but ugly API to avoid needing to check if a level is enabled more than once. This seems like overkill for this internal use:

https://pkg.go.dev/go.uber.org/zap#Logger.Check

Also: having poked around some profiles yesterday, there may be ONE more place where this check might be useful. However, I'll wait until we get this merged before I look again.

Copy link
Contributor

@knusbaum knusbaum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.

@Julio-Guerra Julio-Guerra added this to the 1.37.0 milestone Mar 8, 2022
@Julio-Guerra Julio-Guerra merged commit 5b114be into v1 Mar 8, 2022
@Julio-Guerra Julio-Guerra deleted the evan.jones/log-debugenabled branch March 8, 2022 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants