-
Notifications
You must be signed in to change notification settings - Fork 773
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
Performance optimization low-hanging fruit #775
Comments
My intuition is Common configs should be written in JS datastructures not JSON. You can then more easily arrange them to allow easy access. Generally I think Common is a bit over-abstracted and it'd be better to break the configs related to each package and move it to the package directly. Whatever config has to be used by multiple packages (if any) can remain in Common.
This is very prevalent as you mention. In the VM one valid use-case is using them as keys for |
A major performance drain when running mainnet blocks seems to be emitting the As a temporary solution we can add an option to the VM to disable emitting events for users who don't need it. We should also investigate the |
Reminder for myself from @alcuadrado's comment here:
|
Update: someone at some point should have another look at the initial list here and maybe (directly in the comment) transform to a check list (and check the things already done), otherwise we won't get hold of this. |
Some of these have been resolved thanks to this issue so I will convert to a checklist so we can have a clear picture of remaining items. I can also tag on a per-item basis if it would be a I think |
One idea would be to add some simple dict (or similar) cache and add values on first usage and erase on (generally seldomly occurring I would very much assume) HF or chain switches along Common usage. Did some simple tests on this, might be worth it, script I used: import Common from './src'
let c = new Common({ chain: 'mainnet' })
let dummyCache = {
'value1': 15,
'value2': 15,
'value3': 15,
'value4': 15,
'value5': 15,
'value6': 15,
'value7': 15,
'value8': 15,
'value9': 15,
'value10': 15,
'value11': 15,
'value12': 15,
'minGasLimit': 500,
}
for (const iterations of [1000, 10000, 100000, 1000000, 10000000]) {
let newA, newB
console.log(`Testing with ${iterations} iterations.`)
console.time('T')
for (let i=0; i<iterations; i++) { newA = c.param('gasConfig', 'minGasLimit') }
console.timeEnd('T')
console.time('T')
for (let i=0; i<iterations; i++) { newB = dummyCache['minGasLimit'] }
console.timeEnd('T')
} Results: Testing with 1000 iterations.
T: 5.489ms
T: 0.053ms
Testing with 10000 iterations.
T: 8.378ms
T: 0.421ms
Testing with 100000 iterations.
T: 52.756ms
T: 0.116ms
Testing with 1000000 iterations.
T: 285.341ms
T: 0.824ms
Testing with 10000000 iterations.
T: 2.788s
T: 7.871ms |
Did another small test to put things in perspective and added a global paramCounter += 1
if (paramCounter % 100 == 0) {
console.log(paramCounter)
} For most single state tests this is going into the hundreds, for some in the thousands and for selected in the tens of thousands. The whole state test suite comes to ~8.579.700. So this would be some 2s win for the tests (under the assumption that the cache works well respectively most of the reads would be done from a cache if available). On the benchmark run (so for this 12 or so blocks together with a single iteration) this gets to ~1.130.300 calls, so roughly 200-300ms. Hmm, not clear indicators for me on first sight that this is worth the effort to introduce some cache or something. But also not completely out of question I guess? 🤔 |
I have converted the opening post to a check list. |
Nice! 😄 |
Linking #865 |
I'm creating this issue to list multiple easy refactors that could be implemented to improve the performance of the VM. Some of them come from EthWork's involvement. Others are mine.
Common#param
should be an O(1) operation. It's called an incredible amount of times.The VM's
Memory
class should be implemented with a BufferjumpIsValid
is a linear operation. It can be constant. Update: After talking with Jochem and reading evmone's code, I'm not sure if this is possible nor beneficial.There's a huge number of unnecessary calls to
toString()
. Sometimes it is used instead ofBuffer#equals
. Sometimes it's called to initialize aBN
from aBuffer
, instead of just passing theBuffer
. There obvious instances of this in these functions:Block#validateTransactionsTrie
Block#validateUnclesHash
Block#validateUncles
Account#isContract
Account#setCode
Account#isEmpty
FakeTransaction#hash
Transaction#toCreationAddress
EEI#getExternalBalance
PUSH
's handler: creats a string to create a BN instead of just creating the BNVM#runBlock
: multiple comparisons of Buffers by stringVM#runCall
VM#runTx
: calls toString on a Buffer instead of just checking its sizeDefaultStateManager#accountIsEmpty
The opcodes
handlers
map is indexed by string, it should be done by number.The opcodes
handlers
map and theopcodes
OpcodeList
's one, should probably beMap
s. The semantic ofMap
is way simpler than an accessing an object withobject[key]
. Done in Use Map for OpcodeList and opcode handlers #852Transaction#getDataFee
is called very frequently and super slow:It should be cached
Calls
Common#param
(which is really slow) within a loop, could be done outside of it.Uses
BN
s in a loop while it could use normal numbers and then transform it toBN
. --BN
operations allow new objectsTransaction#hash
should be cachedBlockHeader#hash
should be cachedThe VM's
getOpcodesForHF
can be cached. It's called a lot and iterates a 256 elements map.The VM
ERROR
enum should not have string values, as those are compared very frequently.The text was updated successfully, but these errors were encountered: