-
Notifications
You must be signed in to change notification settings - Fork 21.6k
core/vm: refactor memory resize #33056
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
Conversation
core/vm/memory.go
Outdated
|
|
||
| // Resize resizes the memory to size | ||
| func (m *Memory) Resize(size uint64) { | ||
| if uint64(m.Len()) < size { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we always reuse the Memory instance without clearing.
// Free returns the memory to the pool.
func (m *Memory) Free() {
// To reduce peak allocation, return only smaller memory instances to the pool.
const maxBufferSize = 16 << 10
if cap(m.store) <= maxBufferSize {
m.store = m.store[:0]
m.lastGasCost = 0
memoryPool.Put(m)
}
}After the resize, the junks will also be visible, for instance:
- T0; memory is empty (the underlying slice is 0x1,0x2)
- T1; memory is resized to 2, the memory becomes [0x1, 0x2]
- T2; the EVM only use the first byte somehow (not sure if it's possible), then the memory becomes [correct-data, junk]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
diff --git a/core/vm/memory.go b/core/vm/memory.go
index 5e11e83748..2e71c16c1a 100644
--- a/core/vm/memory.go
+++ b/core/vm/memory.go
@@ -44,6 +44,8 @@ func (m *Memory) Free() {
// To reduce peak allocation, return only smaller memory instances to the pool.
const maxBufferSize = 16 << 10
if cap(m.store) <= maxBufferSize {
+ clear(m.store) // zero out the bytes before truncating the length
+
m.store = m.store[:0]
m.lastGasCost = 0
memoryPool.Put(m)
@@ -79,7 +81,11 @@ func (m *Memory) Set32(offset uint64, val *uint256.Int) {
// Resize resizes the memory to size
func (m *Memory) Resize(size uint64) {
if uint64(m.Len()) < size {
- m.store = append(m.store, make([]byte, size-uint64(m.Len()))...)
+ if uint64(cap(m.store)) > size {
+ m.store = m.store[:size]
+ } else {
+ m.store = append(m.store, make([]byte, size-uint64(m.Len()))...)
+ }
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made two changes:
- use slicing if
cap(m.store) == size - use
len(m.store)instead ofm.Len(). since we usecap(m.store), we should access the length of the slice in the same way.
Looks like (in some very EVM specific tests) we spent a lot of time
resizing memory. If the underlying array is big enough, we can speed it
up a bit by simply slicing the memory.
goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/core/vm
cpu: Intel(R) Core(TM) Ultra 7 155U
│ /tmp/old.txt │ /tmp/new.txt │
│ sec/op │ sec/op vs base │
Resize-14 6.145n ± 9% 1.854n ± 14% -69.83% (p=0.000 n=10)
│ /tmp/old.txt │ /tmp/new.txt │
│ B/op │ B/op vs base │
Resize-14 5.000 ± 0% 5.000 ± 0% ~ (p=1.000 n=10)
│ /tmp/old.txt │ /tmp/new.txt │
│ allocs/op │ allocs/op vs base │
Resize-14 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
From the blocktest benchmark:
620ms 10.93s (flat, cum) 9.92% of Total
. . 80:func (m *Memory) Resize(size uint64) {
30ms 60ms 81: if uint64(m.Len()) < size {
590ms 10.87s 82: m.store = append(m.store, make([]byte, size-uint64(m.Len()))...)
. . 83: }
. . 84:}
---------
Co-authored-by: Felix Lange <fjl@twurst.com>
Looks like (in some very EVM specific tests) we spent a lot of time resizing memory.
If the underlying array if big enough, we can speed it up a bit though:
From the blocktest benchmark: