clusterversion: benchmark and rm Unmarshal allocs#113043
clusterversion: benchmark and rm Unmarshal allocs#113043craig[bot] merged 4 commits intocockroachdb:masterfrom
Conversation
|
After some experiments, it seems like the allocation is hidden inside |
|
There are more |
Yeah, gogoproto/Protobuf is very inefficient in terms of allocations. We've considered using some other encoding, e.g. Tobi was playing around with FlatBuffers last year, but it hasn't been prioritized. In the short term, it doesn't seem viable to replace it. |
|
I think there was a reason why we use |
Interesting. When looking at code, I concluded that |
Yeah, looks about right, this is probably fine then. |
|
We do have this lint indeed. |
|
That's what I thought: It's probably okay anyway, but I'll have a closer look at this when I have time. |
|
The linter was introduced here: #18455. I think it's overly restrictive. It costs unnecessary allocations. I tried generics: func unmarshal[Message protoutil.Message](m Message, b []byte) error {
m.Reset()
return m.Unmarshal(b)
}
...
if err := unmarshal[*ClusterVersion](&curVer, encoded.([]byte)); err != nil {
log.Fatalf(ctx, "%v", err)
}but it doesn't remove the allocation. I don't think the calls are inlined in this case. So the only thing we can do is either lift or workaround this restriction. Maybe we can silence the linter knowing that this case is safe. |
There was a problem hiding this comment.
So the only thing we can do is either lift or workaround this restriction. Maybe we can silence the linter knowing that this case is safe.
You can add an exception for clusterversion/setting.go here, and add a comment at the call sites for why we don't use protoutil.Unmarshal():
As @dt pointed out, it seems unfortunate that we're even unmarshaling here, since isActive() is commonly used in hot paths. Let's write up an issue for this.
pkg/clusterversion/setting.go
Outdated
There was a problem hiding this comment.
We don't strictly need to change all of these, they're not hot paths. I think activeVersionOrEmpty, and possibly Decode, are the ones we care about.
There was a problem hiding this comment.
+1
Honestly we could also just move clusterversion.proto into another package that pkg/settings is allowed to depend on and then store a *ClusterVersion in the atomic instead of a []byte so there'd be no unmarshal -- allocating or otherwise -- on the hot path. But that's a bigger change, so if you're eyeing a back port to resolve an observed customer issue, this more targeted removal of just the alloc, and just on the hot path, probably makes sense for now.
There was a problem hiding this comment.
Leaving as is so that this file consistently uses one way. LMK if feel strongly.
I will file an issue to get rid of protos in this package (and added a TODO in the code).
#113385
|
Btw, I think we could add a generic func UnmarshalNew[M Message](data []byte) (M, error) {
var m M
return m.Unmarshal(data)
} |
Tried that: func UnmarshalNew[M Message](data []byte) (M, error) {
var m M
err := m.Unmarshal(data)
return m, err
}
// and then
curVer, err := protoutil.UnmarshalNew[ClusterVersion](encoded.([]byte))
if err != nil {
log.Fatalf(ctx, "%v", err)
}This does not build: Setting the |
Can do Either way, as you pointed out on Slack, this may not help with performance anyway due to how Go monomorphizes generic functions. |
21a83e1 to
3969f04
Compare
|
bors r=erikgrinaker |
|
🕐 Waiting for PR status (GitHub check) to be set, probably by CI. Bors will automatically try to run when all required PR statuses are set. |
Currently, each IsActive does a memory allocation: ``` ==================== Test output for //pkg/clusterversion:clusterversion_test: goos: darwin goarch: arm64 BenchmarkClusterVersionSettingIsActive BenchmarkClusterVersionSettingIsActive-10 28778041 42.03 ns/op 16 B/op 1 allocs/op PASS ``` Since the cluster version check is in many hot paths, we should eliminate this allocation. Epic: none Release note: none
The allocation no longer presents itself in the benchmark. ``` ==================== Test output for //pkg/clusterversion:clusterversion_test: goos: darwin goarch: arm64 BenchmarkClusterVersionSettingIsActive BenchmarkClusterVersionSettingIsActive-10 45417914 26.43 ns/op 0 B/op 0 allocs/op PASS ``` Epic: none Release note: none
Epic: none Release note: none
The mandated protoutil.Unmarshal caused unnecessary allocations in clusterversion/setting.go, is used extensively on critical paths. These allocations were visible on benchmarks (e.g. 1% of all allocations in one KV benchmark). Epic: none Release note: none
3969f04 to
6a935ae
Compare
|
bors retry |
|
Build succeeded: |
Currently, each
IsActivedoes a memory allocation:Since the cluster version check is in many hot paths, we should eliminate this allocation.
After:
Touches #111561
Epic: none
Release note: none