Skip to content

Conversation

@MariusVanDerWijden
Copy link
Member

@MariusVanDerWijden MariusVanDerWijden commented Oct 23, 2025

Reduces the allocation in certain methods. This will reduce overall allocations by 5-10% during snap sync.
The issue is that the compiler does not know that the keys are const, thus it can't allocate the keys on the stack.
All methods that take in var length parameters, e.g trienodeHistoryIndexKey can't be optimized like this, since the compiler does not know that path is only of a certain size

goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/core/rawdb
cpu: Intel(R) Core(TM) Ultra 7 155U
                                     │ /tmp/old.txt  │             /tmp/new.txt             │
                                     │    sec/op     │    sec/op     vs base                │
Keys/headerNumberKey-14                43.850n ±  5%   5.733n ±  2%  -86.93% (p=0.000 n=10)
Keys/headerHashKey-14                  29.025n ±  3%   8.069n ±  1%  -72.20% (p=0.000 n=10)
Keys/blockBodyKey-14                   63.590n ±  6%   8.756n ±  3%  -86.23% (p=0.000 n=10)
Keys/blockReceiptsKey-14               60.945n ±  3%   9.149n ± 23%  -84.99% (p=0.000 n=10)
Keys/txLookupKey-14                    37.010n ±  6%   7.470n ±  6%  -79.82% (p=0.000 n=10)
Keys/accountSnapshotKey-14             38.640n ± 12%   7.099n ±  7%  -81.63% (p=0.000 n=10)
Keys/storageSnapshotKey-14              50.05n ± 23%   12.19n ±  9%  -75.65% (p=0.000 n=10)
Keys/storageSnapshotsKey-14            38.010n ± 10%   7.052n ±  5%  -81.45% (p=0.000 n=10)
Keys/skeletonHeaderKey-14              23.710n ±  4%   5.656n ± 25%  -76.14% (p=0.000 n=10)
Keys/preimageKey-14                    38.875n ± 18%   6.359n ± 23%  -83.64% (p=0.000 n=10)
Keys/codeKey-14                        36.650n ± 17%   6.248n ±  5%  -82.95% (p=0.000 n=10)
Keys/configKey-14                      36.390n ±  3%   6.502n ± 12%  -82.13% (p=0.000 n=10)
Keys/genesisStateSpecKey-14            43.110n ±  8%   6.339n ± 11%  -85.30% (p=0.000 n=10)
Keys/stateIDKey-14                     35.835n ±  4%   5.626n ±  1%  -84.30% (p=0.000 n=10)
Keys/accountTrieNodeKey-14              19.18n ± 15%   14.92n ±  2%  -22.21% (p=0.000 n=10)
Keys/storageTrieNodeKey-14              37.94n ±  3%   33.13n ± 19%  -12.68% (p=0.023 n=10)
Keys/filterMapRowKey-14                19.915n ±  2%   6.301n ± 10%  -68.36% (p=0.000 n=10)
Keys/filterMapLastBlockKey-14          15.125n ±  5%   5.127n ± 32%  -66.11% (p=0.000 n=10)
Keys/filterMapBlockLVKey-14            20.040n ± 20%   5.516n ±  4%  -72.48% (p=0.000 n=10)
Keys/accountHistoryIndexKey-14         37.025n ±  3%   5.756n ± 11%  -84.45% (p=0.000 n=10)
Keys/storageHistoryIndexKey-14         49.655n ±  7%   9.871n ±  2%  -80.12% (p=0.000 n=10)
Keys/trienodeHistoryIndexKey-14         37.51n ±  3%   31.36n ± 13%  -16.40% (p=0.000 n=10)
Keys/accountHistoryIndexBlockKey-14    36.215n ±  5%   9.112n ± 22%  -74.84% (p=0.000 n=10)
Keys/storageHistoryIndexBlockKey-14     48.99n ±  1%   17.18n ±  7%  -64.92% (p=0.000 n=10)
Keys/trienodeHistoryIndexBlockKey-14    39.02n ±  4%   41.87n ±  8%   +7.32% (p=0.029 n=10)
Keys/transitionStateKey-14             40.215n ±  2%   6.641n ± 13%  -83.49% (p=0.000 n=10)
geomean                                 35.58n         8.975n        -74.78%

                                     │ /tmp/old.txt │              /tmp/new.txt               │
                                     │     B/op     │    B/op     vs base                     │
Keys/headerNumberKey-14                  48.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/headerHashKey-14                    16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/blockBodyKey-14                     64.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/blockReceiptsKey-14                 64.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/txLookupKey-14                      48.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/accountSnapshotKey-14               48.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/storageSnapshotKey-14               80.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/storageSnapshotsKey-14              48.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/skeletonHeaderKey-14                16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/preimageKey-14                      48.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/codeKey-14                          48.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/configKey-14                        48.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/genesisStateSpecKey-14              64.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/stateIDKey-14                       48.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/accountTrieNodeKey-14               8.000 ± 0%   8.000 ± 0%         ~ (p=1.000 n=10) ¹
Keys/storageTrieNodeKey-14               48.00 ± 0%   48.00 ± 0%         ~ (p=1.000 n=10) ¹
Keys/filterMapRowKey-14                  16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/filterMapLastBlockKey-14            8.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/filterMapBlockLVKey-14              16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/accountHistoryIndexKey-14           48.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/storageHistoryIndexKey-14           80.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/trienodeHistoryIndexKey-14          48.00 ± 0%   48.00 ± 0%         ~ (p=1.000 n=10) ¹
Keys/accountHistoryIndexBlockKey-14      48.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/storageHistoryIndexBlockKey-14      80.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
Keys/trienodeHistoryIndexBlockKey-14     48.00 ± 0%   48.00 ± 0%         ~ (p=1.000 n=10) ¹
Keys/transitionStateKey-14               64.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
geomean                                  39.16                    ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

                                     │ /tmp/old.txt │              /tmp/new.txt               │
                                     │  allocs/op   │ allocs/op   vs base                     │
Keys/headerNumberKey-14                  1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/headerHashKey-14                    1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/blockBodyKey-14                     2.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/blockReceiptsKey-14                 2.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/txLookupKey-14                      1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/accountSnapshotKey-14               1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/storageSnapshotKey-14               1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/storageSnapshotsKey-14              1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/skeletonHeaderKey-14                1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/preimageKey-14                      1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/codeKey-14                          1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/configKey-14                        1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/genesisStateSpecKey-14              1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/stateIDKey-14                       1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/accountTrieNodeKey-14               1.000 ± 0%   1.000 ± 0%         ~ (p=1.000 n=10) ¹
Keys/storageTrieNodeKey-14               1.000 ± 0%   1.000 ± 0%         ~ (p=1.000 n=10) ¹
Keys/filterMapRowKey-14                  1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/filterMapLastBlockKey-14            1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/filterMapBlockLVKey-14              1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/accountHistoryIndexKey-14           1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/storageHistoryIndexKey-14           1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/trienodeHistoryIndexKey-14          1.000 ± 0%   1.000 ± 0%         ~ (p=1.000 n=10) ¹
Keys/accountHistoryIndexBlockKey-14      1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/storageHistoryIndexBlockKey-14      1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
Keys/trienodeHistoryIndexBlockKey-14     1.000 ± 0%   1.000 ± 0%         ~ (p=1.000 n=10) ¹
Keys/transitionStateKey-14               1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
geomean                                  1.055                    ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

@MariusVanDerWijden
Copy link
Member Author

Pls don't merge yet, I have some more improvements in the pipeline

@MariusVanDerWijden
Copy link
Member Author

Benchmark

func BenchmarkKeys(b *testing.B) {
	l := common.Hash{1}
	m := common.Hash{2}
	number := uint64(123)
	number32 := uint32(123)
	path := []byte{1, 2, 3}
	b.Run("headerNumberKey", func(b *testing.B) {
		fn := func() []byte { return headerNumberKey(l) }
		for range b.N {
			fn()
		}
	})
	b.Run("headerHashKey", func(b *testing.B) {
		fn := func() []byte { return headerHashKey(number) }
		for range b.N {
			fn()
		}
	})
	b.Run("blockBodyKey", func(b *testing.B) {
		fn := func() []byte { return blockBodyKey(number, l) }
		for range b.N {
			fn()
		}
	})
	b.Run("blockReceiptsKey", func(b *testing.B) {
		fn := func() []byte { return blockReceiptsKey(number, l) }
		for range b.N {
			fn()
		}
	})
	b.Run("txLookupKey", func(b *testing.B) {
		fn := func() []byte { return txLookupKey(l) }
		for range b.N {
			fn()
		}
	})
	b.Run("accountSnapshotKey", func(b *testing.B) {
		fn := func() []byte { return accountSnapshotKey(l) }
		for range b.N {
			fn()
		}
	})
	b.Run("storageSnapshotKey", func(b *testing.B) {
		fn := func() []byte { return storageSnapshotKey(l, m) }
		for range b.N {
			fn()
		}
	})
	b.Run("storageSnapshotsKey", func(b *testing.B) {
		fn := func() []byte { return storageSnapshotsKey(l) }
		for range b.N {
			fn()
		}
	})
	b.Run("skeletonHeaderKey", func(b *testing.B) {
		fn := func() []byte { return skeletonHeaderKey(number) }
		for range b.N {
			fn()
		}
	})
	b.Run("preimageKey", func(b *testing.B) {
		fn := func() []byte { return preimageKey(l) }
		for range b.N {
			fn()
		}
	})
	b.Run("codeKey", func(b *testing.B) {
		fn := func() []byte { return codeKey(l) }
		for range b.N {
			fn()
		}
	})
	b.Run("configKey", func(b *testing.B) {
		fn := func() []byte { return configKey(l) }
		for range b.N {
			fn()
		}
	})
	b.Run("genesisStateSpecKey", func(b *testing.B) {
		fn := func() []byte { return genesisStateSpecKey(l) }
		for range b.N {
			fn()
		}
	})
	b.Run("stateIDKey", func(b *testing.B) {
		fn := func() []byte { return stateIDKey(l) }
		for range b.N {
			fn()
		}
	})
	b.Run("accountTrieNodeKey", func(b *testing.B) {
		fn := func() []byte { return accountTrieNodeKey(path) }
		for range b.N {
			fn()
		}
	})
	b.Run("storageTrieNodeKey", func(b *testing.B) {
		fn := func() []byte { return storageTrieNodeKey(l, path) }
		for range b.N {
			fn()
		}
	})
	b.Run("filterMapRowKey", func(b *testing.B) {
		fn := func() []byte { return filterMapRowKey(number, true) }
		for range b.N {
			fn()
		}
	})
	b.Run("filterMapLastBlockKey", func(b *testing.B) {
		fn := func() []byte { return filterMapLastBlockKey(number32) }
		for range b.N {
			fn()
		}
	})
	b.Run("filterMapBlockLVKey", func(b *testing.B) {
		fn := func() []byte { return filterMapBlockLVKey(number) }
		for range b.N {
			fn()
		}
	})
	b.Run("accountHistoryIndexKey", func(b *testing.B) {
		fn := func() []byte { return accountHistoryIndexKey(l) }
		for range b.N {
			fn()
		}
	})
	b.Run("storageHistoryIndexKey", func(b *testing.B) {
		fn := func() []byte { return storageHistoryIndexKey(l, m) }
		for range b.N {
			fn()
		}
	})
	b.Run("trienodeHistoryIndexKey", func(b *testing.B) {
		fn := func() []byte { return trienodeHistoryIndexKey(l, path) }
		for range b.N {
			fn()
		}
	})
	b.Run("accountHistoryIndexBlockKey", func(b *testing.B) {
		fn := func() []byte { return accountHistoryIndexBlockKey(l, number32) }
		for range b.N {
			fn()
		}
	})
	b.Run("storageHistoryIndexBlockKey", func(b *testing.B) {
		fn := func() []byte { return storageHistoryIndexBlockKey(l, m, number32) }
		for range b.N {
			fn()
		}
	})
	b.Run("trienodeHistoryIndexBlockKey", func(b *testing.B) {
		fn := func() []byte { return trienodeHistoryIndexBlockKey(l, path, number32) }
		for range b.N {
			fn()
		}
	})
	b.Run("transitionStateKey", func(b *testing.B) {
		fn := func() []byte { return transitionStateKey(l) }
		for range b.N {
			fn()
		}
	})
}

@MariusVanDerWijden
Copy link
Member Author

We could even reduce the allocations of the path based methods, by allocating a fixed size array longer than the key and cutting it down to the key length, but for that I would need to know the maximum key length cc @rjl493456442

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.

2 participants