perf: use autoresearch to reduce allocations#3225
Conversation
Result: {"status":"keep","allocs_per_op":81,"bytes_per_op":25934,"ns_per_op":34001}
…allocations per block
Result: {"status":"keep","allocs_per_op":79,"bytes_per_op":25697,"ns_per_op":34147}
… make([][]byte, n) allocation
Result: {"status":"keep","allocs_per_op":78,"bytes_per_op":22996,"ns_per_op":33091}
…pper and txsToByteSlices allocations
Result: {"status":"keep","allocs_per_op":77,"bytes_per_op":20276,"ns_per_op":32480}
…allocation
Result: {"status":"keep","allocs_per_op":74,"bytes_per_op":12192,"ns_per_op":31624}
…10 allocs per block Replace per-call allocation of pb.Header/pb.Version/pb.Data/pb.Metadata with sync.Pool reuse in the hot MarshalBinary path. ToProto() API is unchanged — only MarshalBinary is affected since it consumes the result immediately. Metrics (100_txs benchmark): - 74 → 64 allocs/op (-13.5%) - ~12.1 → ~11.1 KB (-8.3%) - ~31ns flat
…Signer/pb.Version Eliminates 4 struct allocations per signed header marshal: pb.SignedHeader, pb.Header, pb.Version, pb.Signer. These are now borrowed from sync.Pool and returned after proto.Marshal completes. Metrics (100_txs benchmark): - 64 → 56 allocs/op - ~11KB → ~10.2KB
…er block State.ToProto allocated pb.State + pb.Version + timestamppb.Timestamp per block. MarshalBinary now pools those structs and returns the marshaled bytes directly. pkg/store/batch.UpdateState switched from ToProto+proto.Marshal to MarshalBinary.
📝 WalkthroughWalkthroughThis pull request introduces memory optimization strategies across the codebase, including object pooling for protobuf messages and SHA256 hash instances, unsafe slice conversions to avoid allocations, replacement of protobuf marshalling with direct binary marshalling, and compile-time type safety checks. The changes span serialization, hashing, and execution layers. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).
|
|
Claude finished @tac0turtle's task in 5m 48s —— View job Code ReviewTasks:
Good allocation-reduction work overall. The Critical / Correctness1.
Additionally, the cleanup block is copy-pasted three times. A panic in func (sh *SignedHeader) MarshalBinary() ([]byte, error) {
psh := pbSignedHeaderPool.Get().(*pb.SignedHeader)
ph := pbHeaderPool.Get().(*pb.Header)
pv := pbVersionPool.Get().(*pb.Version)
psi := pbSignerPool.Get().(*pb.Signer)
defer func() {
psh.Reset(); pbSignedHeaderPool.Put(psh)
ph.Reset(); pbHeaderPool.Put(ph)
pv.Reset(); pbVersionPool.Put(pv)
psi.Reset(); pbSignerPool.Put(psi)
}()
// ... fill and marshal ...
}2. pm.LastDataHash = d.LastDataHash // direct alias, no copy
Moderate3. // executor.go:801
rawTxs = unsafe.Slice((*[]byte)(unsafe.SliceData(data.Txs)), n)
4. // hashing.go:130
pbData := pb.Data{Txs: unsafe.Slice((*[]byte)(unsafe.SliceData(d.Txs)), len(d.Txs))}
Minor5.
6. pts := ×tamppb.Timestamp{...} // heap allocation not eliminated
7. No If Testing8. Missing safety test for the
9.
Positive Notes
|
|
9148ccc to
2438dc0
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3225 +/- ##
==========================================
+ Coverage 61.67% 62.06% +0.38%
==========================================
Files 120 120
Lines 12635 12778 +143
==========================================
+ Hits 7793 7931 +138
- Misses 3968 3975 +7
+ Partials 874 872 -2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🧹 Nitpick comments (2)
types/hash_memo_bench_test.go (1)
35-42: Consider invalidating cache between iterations for consistent baseline.In
BenchmarkHeaderHash_Single, the header is created once outside the loop. WhileHash()doesn't setcachedHash, if future refactoring changes this behavior, the benchmark would measure cached hits after the first iteration. Consider addingh.InvalidateHash()inside the loop for robustness:♻️ Suggested improvement for robustness
func BenchmarkHeaderHash_Single(b *testing.B) { h := GetRandomHeader("bench-chain", GetRandomBytes(32)) b.ReportAllocs() b.ResetTimer() for b.Loop() { + h.InvalidateHash() _ = h.Hash() } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@types/hash_memo_bench_test.go` around lines 35 - 42, BenchmarkHeaderHash_Single currently constructs h once and calls h.Hash() repeatedly; to ensure each iteration measures an uncached baseline, call h.InvalidateHash() before each h.Hash() invocation (or recreate h inside the loop using GetRandomHeader/GetRandomBytes) so any cachedHash is cleared; update the loop in BenchmarkHeaderHash_Single to invoke h.InvalidateHash() (or reinitialize h) on every iteration before calling h.Hash().types/hashing.go (1)
128-135: Ignored marshal error could silently produce incorrect commitment.Line 131 ignores the error from
proto.Marshal. While this is consistent with the existing pattern inHash()(line 120-121), a marshal failure would produce a hash of nil/empty bytes, potentially causing consensus issues. Consider at least logging failures in debug builds or returning an error.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@types/hashing.go` around lines 128 - 135, DACommitment currently ignores the error from proto.Marshal which can yield an incorrect commitment; change DACommitment to surface marshal failures instead of silently hashing nil by returning (Hash, error) and propagate this change to callers (mirror the new signature across any places invoking DACommitment), or if changing signature is impractical, check the error and log it via the package logger in a debug build and return a zero-value Hash; locate DACommitment and the related Hash() usage to implement proper error handling and ensure any callers handle the returned error or the logged failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@types/hash_memo_bench_test.go`:
- Around line 35-42: BenchmarkHeaderHash_Single currently constructs h once and
calls h.Hash() repeatedly; to ensure each iteration measures an uncached
baseline, call h.InvalidateHash() before each h.Hash() invocation (or recreate h
inside the loop using GetRandomHeader/GetRandomBytes) so any cachedHash is
cleared; update the loop in BenchmarkHeaderHash_Single to invoke
h.InvalidateHash() (or reinitialize h) on every iteration before calling
h.Hash().
In `@types/hashing.go`:
- Around line 128-135: DACommitment currently ignores the error from
proto.Marshal which can yield an incorrect commitment; change DACommitment to
surface marshal failures instead of silently hashing nil by returning (Hash,
error) and propagate this change to callers (mirror the new signature across any
places invoking DACommitment), or if changing signature is impractical, check
the error and log it via the package logger in a debug build and return a
zero-value Hash; locate DACommitment and the related Hash() usage to implement
proper error handling and ensure any callers handle the returned error or the
logged failure.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 12528a06-2158-4aad-8475-8990e96b4bcb
📒 Files selected for processing (7)
block/internal/executing/executor.gopkg/store/batch.gotypes/hash_memo_bench_test.gotypes/hashing.gotypes/serialization.gotypes/serialization_test.gotypes/tx.go
Overview
This pr used autoresearch to run a loop on reducing allocations
3 a8b6352 78allocs 22,996 33,091 keep Unsafe reinterpret cast of Txs to [][]byte in ApplyBlock — eliminates make([][]byte, n) allocation
4 823aa62 77allocs 20,276 32,480 keep Direct pb.Data serialization in DACommitment — avoids pruned Data wrapper and txsToByteSlices allocations
5 0720b44 74allocs 12,192 31,624 keep unsafe.Slice in Data.ToProto() — eliminates txsToByteSlices [][]byte allocation
6 — 74allocs 12,187 31,217 discard Reverted hand-written HashSlim wire encoder — produced different hashes than MarshalBinary
7 ccbc2e4 64allocs 11,130 31,570 keep sync.Pool for protobuf message structs in MarshalBinary — eliminates 10 allocs per block
8 805672e 56allocs 10,217 31,037 keep Pooled SignedHeader.MarshalBinary — reuse pb.SignedHeader/pb.Header/pb.Signer/pb.Version structs
Summary by CodeRabbit
Tests
Performance
Bug Fixes