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

Rearch for performance: BREAKING CHANGES #42

Merged
merged 6 commits into from
May 28, 2021
Merged

Conversation

thockin
Copy link
Contributor

@thockin thockin commented Apr 2, 2021

Playing with ideas from Russ Cox.

xref #40

Before:

$ go test -bench='.' ./benchmark/
goos: linux
goarch: amd64
pkg: github.com/go-logr/logr/benchmark
cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz
BenchmarkInfoOneArg-6             483949              2420 ns/op
BenchmarkInfoSeveralArgs-6        359826              3302 ns/op
BenchmarkV0Info-6                 352570              3399 ns/op
BenchmarkV9Info-6                9096120               130.3 ns/op
BenchmarkError-6                  366549              3280 ns/op
BenchmarkWithValues-6            9936550               120.1 ns/op
BenchmarkWithName-6             22592770                53.38 ns/op

After:

$ go test -bench='.' ./benchmark/
goos: linux
goarch: amd64
pkg: github.com/go-logr/logr/benchmark
cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz
BenchmarkInfoOneArg-6             514122              2291 ns/op
BenchmarkInfoSeveralArgs-6        380796              3153 ns/op
BenchmarkV0Info-6                 376472              3150 ns/op
BenchmarkV9Info-6               15167118                79.13 ns/op
BenchmarkError-6                  358990              3336 ns/op
BenchmarkWithValues-6            7674392               155.9 ns/op
BenchmarkWithName-6             23391848                50.33 ns/op

Logs that actually write are dominated by the cost of preparing the
message. Logs which do not write (e.g. V(9)) are much faster here
(thog glog raw is much faster still). The main difference is the
variadic slice setup. This is a candidate for Go to optimize - it now
knows that the args are only used inside the conditional (Enabled()),
and when that path is not executed, it can elide that work.

The real rub is that this is a breaking change for implementors (but not for consumers). We haven't declared 1.0 yet, but I know we have some users out there. @wojas

@thockin thockin changed the title Perf ideas logr Perf ideas Apr 2, 2021
@thockin thockin force-pushed the perf-ideas branch 3 times, most recently from 42bb35d to ecb1e88 Compare April 8, 2021 22:55
@thockin
Copy link
Contributor Author

thockin commented Apr 21, 2021

@wojas @DirectXMan12

It's been a crazy few months. If you (or others) have time to look at this (specifically the last commit) it would be super helpful

@thockin thockin changed the title logr Perf ideas WIP: logr performance ideas Apr 21, 2021
@wojas
Copy link
Contributor

wojas commented Apr 22, 2021

I will have a look at it. This is a rather big breaking change, I need some time to digest it.

Copy link
Contributor

@wojas wojas left a comment

Choose a reason for hiding this comment

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

I need some more time to digest the implications.

I like that the change is mostly transparent to log callers, it just affects backends and log initialization code.

The concrete type also provides more possibilities for extending the interface in the future. For example, when adding a new method like WithFoo:

  • The LogSink interface could be kept the same to not break existing backends
  • The Logger concrete type can safely add the new receiver
  • This new function would test if the sink implements the new interface and provide some sensible fallback behavior for older backends.

logr.go Outdated Show resolved Hide resolved
logr.go Outdated Show resolved Hide resolved
funcr/funcr.go Outdated Show resolved Hide resolved
logr.go Outdated
// flags might be used to set the logging verbosity and disable some info
// logs.
Enabled() bool
Enabled(level int) bool
Copy link
Contributor

Choose a reason for hiding this comment

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

At this moment it's only the level, but perhaps we would like to pull some other information into the concrete Logger in the future, which would require breaking changes to pass it to the Logger.

One alternative would be to pass it in a struct:

type LogContext struct {
    level int
}

And then change the interface to:

Enabled(logctx LogContext) bool
Info(logctx LogContext, msg string, keysAndValues ...interface{})
...

Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps this LogContext can also contain the number of stack frames added by the log stack. This would allow the creation of middleware-like log sinks that delegate to other sinks, while keeping the call stack count correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perhaps this LogContext can also contain the number of stack frames added by the log stack. This would allow the creation of middleware-like log sinks that delegate to other sinks, while keeping the call stack count correct.

This is what WithCallDepth() is for. I agree that assuming literal 1 in the LogSink is kind of a mess, though. Passing it to every call to Info() feels a bit silly - it's not going to change, like the levels. It almost feels like logr.CallFrameCount() (simple, ugly) or something like:

logr.New(sink) calls sink.Init(numCallFrames), (probably a struct) which can choose to store that info if it needs.

In real code, the definition of the Logger would be far away from the
logging call-sites.  The benchmark now forces the compiler to NOT
inline, even though it could.  It also now tests logr.Discard() and
funcr.
@thockin
Copy link
Contributor Author

thockin commented Apr 23, 2021

On compat: There are some things that are just awkward after this, like DiscardLogger (the type) no longer making sense (it's a LogSink). We could jump thru hoops to make things work (make DiscardLogger a struct that embeds a Logger?) or we could just make this conversion a big breaking change. MOST clients will be fine with a recompile, and a small number will hit edge cases.

If we do that, we can EOL InfoLogger and a few other vestigial things.

Copy link
Contributor

@DirectXMan12 DirectXMan12 left a comment

Choose a reason for hiding this comment

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

I like that it appears to be non-breaking to most uses, except for custom implementations. The perf improvement for the V logger is nice. Theoretically, this could cause more work for immutable logging-level implementations (if you have an implementation that returns NullLogger from V()` if it's disabled, we now have an extra check every time), but practically I'm not sure I'm concerned about that as much (and many of the logging implementations don't have that property for various reasons, anyway).

With the style of optional implementations you've got (extra potentially nil field in the concrete logger), if we grow several optional interfaces, this could cause an increase in the size of concrete Logger struct and I'd be curious to see if that has perf implications.

Convert Logger from an interfact to a concrete type that calls out to a
LogSink interface.

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!                                                         !!
!! THIS IS A BREAKING CHANGE                               !!
!!                                                         !!
!! Most end-users just need to recompile.                  !!
!!                                                         !!
!! Logging implementations need to make code changes.      !!
!!                                                         !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Before:
```
$ go test -bench='.' ./benchmark/
goos: linux
goarch: amd64
pkg: github.com/go-logr/logr/benchmark
cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz
BenchmarkInfoOneArg-6        	 3013077	       400.9 ns/op
BenchmarkInfoSeveralArgs-6   	 1162088	      1039 ns/op
BenchmarkV0Info-6            	  754347	      1418 ns/op
BenchmarkV9Info-6            	 4605950	       267.8 ns/op
BenchmarkError-6             	  935028	      1135 ns/op
BenchmarkWithValues-6        	 5127631	       232.5 ns/op
BenchmarkWithName-6          	12850569	       106.5 ns/op
```

After:
```
$ go test -bench='.' ./benchmark/
goos: linux
goarch: amd64
pkg: github.com/go-logr/logr/benchmark
cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz
BenchmarkInfoOneArg-6        	 2465788	       475.7 ns/op
BenchmarkInfoSeveralArgs-6   	  893026	      1226 ns/op
BenchmarkV0Info-6            	  817473	      1250 ns/op
BenchmarkV9Info-6            	 8595180	       155.1 ns/op
BenchmarkError-6             	 1000000	      1371 ns/op
BenchmarkWithValues-6        	 3902214	       292.0 ns/op
BenchmarkWithName-6          	11271037	       106.6 ns/op
```

Logs that actually write are dominated by the cost of preparing the
message.  Logs which do not write (e.g. `V(9)`) are faster here
(thog glog raw is much faster still).  The main difference is the
variadic slice setup.  This is a candidate for Go to optimize - it now
knows that the args are only used inside the conditional (`Enabled()`),
and when that path is not executed, it can elide that work.
This allows logr to pass params about internal stuff (such as the number
of call frames it adds) to LogSink implementations.
DiscardLogger became a LogSink, so leaving that as a type was only
needed for NullLogger.

NullLogger can be completely replaced with Discard().

InfoLogger is vestigial and since this is a breaking change anyway,
let's kill it off.

Not that Logger is a type, we do not need WithCallDepth() as a
standalone anymore.
@thockin
Copy link
Contributor Author

thockin commented May 16, 2021

Added 2 new commits.

  1. Add a LogSink.Init() which receives info like call depth.
  2. Remove back-compat.

I'm feeling pretty good about this overall. I propose we adopt this PR, call it our last intentional breaking change, and call it 1.0.0. Maybe bolster the docs and tests a bit before that.

$ GO111MODULE=off go test -bench=. ./benchmark/
goos: linux
goarch: amd64
pkg: github.com/go-logr/logr/benchmark
cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz
BenchmarkDiscardInfoOneArg-6        	19445642	        55.71 ns/op
BenchmarkDiscardInfoSeveralArgs-6   	 7857495	       156.5 ns/op
BenchmarkDiscardV0Info-6            	 7795311	       160.5 ns/op
BenchmarkDiscardV9Info-6            	 7694216	       158.0 ns/op
BenchmarkDiscardError-6             	 7300124	       163.9 ns/op
BenchmarkDiscardWithValues-6        	17942917	        78.88 ns/op
BenchmarkDiscardWithName-6          	278674214	         3.797 ns/op
BenchmarkFuncrInfoOneArg-6          	 2485114	       495.4 ns/op
BenchmarkFuncrInfoSeveralArgs-6     	  894175	      1311 ns/op
BenchmarkFuncrV0Info-6              	  925024	      1255 ns/op
BenchmarkFuncrV9Info-6              	 7969364	       161.1 ns/op
BenchmarkFuncrError-6               	  961226	      1412 ns/op
BenchmarkFuncrWithValues-6          	 4106169	       293.7 ns/op
BenchmarkFuncrWithName-6            	92719092	        12.27 ns/op
PASS
ok  	github.com/go-logr/logr/benchmark	21.148s

@thockin thockin changed the title WIP: logr performance ideas Rearch for performance: BREAKING CHANGES May 28, 2021
@thockin
Copy link
Contributor Author

thockin commented May 28, 2021

OK, I am taking silence as consent.

I will look at docs and tests and call this v1.0.0-rc1

@thockin thockin merged commit 141f03a into go-logr:master May 28, 2021
}
l.prefix += name
l2.prefix += name
return l
Copy link

Choose a reason for hiding this comment

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

Should this be returning l2?

Copy link

Choose a reason for hiding this comment

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

Just spotted #47. Sorry for the noise.

@thockin
Copy link
Contributor Author

thockin commented May 29, 2021 via email

thbkrkr added a commit to thbkrkr/cloud-on-k8s that referenced this pull request Dec 15, 2021
Relates to the breaking change go-logr/logr#42
released in logr v1.0.0.
thbkrkr pushed a commit to elastic/cloud-on-k8s that referenced this pull request Jan 4, 2022
* Update module sigs.k8s.io/controller-runtime to v0.11.0

* Run generate

* Replace corev1.Handler by corev1.LifecycleHandler

* Replace corev1.Handler by corev1.ProbeHandler

* Use logr.Discard()
Relates to the breaking change go-logr/logr#42
released in logr v1.0.0.
naemono pushed a commit to naemono/cloud-on-k8s that referenced this pull request Jan 13, 2022
* Update module sigs.k8s.io/controller-runtime to v0.11.0

* Run generate

* Replace corev1.Handler by corev1.LifecycleHandler

* Replace corev1.Handler by corev1.ProbeHandler

* Use logr.Discard()
Relates to the breaking change go-logr/logr#42
released in logr v1.0.0.
Nabil372 pushed a commit to gocardless/theatre that referenced this pull request Nov 15, 2022
Upgrades controller-runtime from v0.10.3 to v0.11.2.
Breaking changes described here:
https://github.com/kubernetes-sigs/controller-runtime/releases/tag/v0.11.0

There was a breaking change to the `go-logr` module.

Refactored our logging package in order to implement the new
`go-logr` interface.
Referred to the following code/PRs on how to do that:
- kubernetes-sigs/controller-runtime#1709
https://github.com/varshaprasad96/controller-runtime/blob/958ff158cbb375164a9ddac0cdee8ba05ae232b4/pkg/log/log_test.go
go-logr/logr#42

Bumped controller-gen from v0.7.0 to v0.8.0.
https://github.com/kubernetes-sigs/controller-tools/releases/tag/v0.8.0
Nabil372 pushed a commit to gocardless/theatre that referenced this pull request Nov 30, 2022
Upgrades controller-runtime from v0.10.3 to v0.11.2.
Breaking changes described here:
https://github.com/kubernetes-sigs/controller-runtime/releases/tag/v0.11.0

There was a breaking change to the `go-logr` module.

Refactored our logging package in order to implement the new
`go-logr` interface.
Referred to the following code/PRs on how to do that:
- kubernetes-sigs/controller-runtime#1709
https://github.com/varshaprasad96/controller-runtime/blob/958ff158cbb375164a9ddac0cdee8ba05ae232b4/pkg/log/log_test.go
go-logr/logr#42

Bumped controller-gen from v0.7.0 to v0.8.0.
https://github.com/kubernetes-sigs/controller-tools/releases/tag/v0.8.0
fantapsody pushed a commit to fantapsody/cloud-on-k8s that referenced this pull request Feb 7, 2023
* Update module sigs.k8s.io/controller-runtime to v0.11.0

* Run generate

* Replace corev1.Handler by corev1.LifecycleHandler

* Replace corev1.Handler by corev1.ProbeHandler

* Use logr.Discard()
Relates to the breaking change go-logr/logr#42
released in logr v1.0.0.
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