Open radumereuta opened 3 years ago
Let's keep this in mind when we modify the Module class so we don't make the same mistake again.
Provide some numbers. It take time in kompile, but it also takes some time when running proofs. Many proofs, lots of time spent.
Here are some numbers from evm proofs.
Command: kserver ; make proofs -j 12
kompile driver.md -I /home/radu/work/evm-semantics/.build/usr/lib/kevm/include/kframework -I /home/radu/work/evm-semantics/.build/usr/lib/kevm/blockchain-k-plugin/include/kframework --hook-namespaces 'JSON KRYPTO BLOCKCHAIN' --emit-json --md-selector 'k & ! nobytes & ! node' --backend haskell --directory .build/usr/lib/kevm/haskell --main-module ETHEREUM-SIMULATION --syntax-module ETHEREUM-SIMULATION -v --debug -I /usr/lib/kevm/include/kframework -I /usr/lib/kevm/blockchain-k-plugin/include/kframework
Outer parsing [85 modules] = 1.798s
New scanner: STDOUT-STREAM-CONFIG-CELLS = 0.195s
New scanner: STDIN-STREAM-CONFIG-CELLS = 0.227s
New scanner: EVM-CONFIG-CELLS = 0.557s
Parse configurations [3/3 declarations] = 1.303s
New scanner: ETHEREUM-SIMULATION-RULE-CELLS = 1.148s
New scanner: K-IO-RULE-CELLS = 0.378s
New scanner: STDOUT-STREAM-RULE-CELLS = 0.391s
New scanner: STDIN-STREAM-RULE-CELLS = 0.449s
New scanner: ETHEREUM-SIMULATION-RULE-CELLS = 2.609s
Parse rules [1545/1545 rules] = 9.535s
Validate definition = 0.429s
Apply compile pipeline = 4.479s
Kompile to kore = 1.375s
Save to disk = 0.184s
Backend = 5.883s
Cleanup = 0.000s
Total = 25.000s
kprovex --directory tests/specs/benchmarks/verification/haskell tests/specs/benchmarks/staticarray00-spec.k -I /home/radu/work/evm-semantics/.build/usr/lib/kevm/include/kframework -I /home/radu/work/evm-semantics/.build/usr/lib/kevm/blockchain-k-plugin/include/kframework --profile-rule-parsing timings/tests/specs/benchmarks/staticarray00-spec.k.log --format-failures -v --dry-run
Outer parsing [100 modules] = 2.342s
New scanner: VERIFICATION-RULE-CELLS = 2.013s
New scanner: STATICARRAY00-SPEC-RULE-CELLS = 1.712s
Parse spec modules [1/1 rules] = 13.734s
Apply prover steps = 5.537s
kore-exec /home/radu/work/evm-semantics/./tests/specs/benchmarks/verification/haskell/verification-kompiled/definition.kore --module VERIFICATION --prove /home/radu/work/evm-semantics/./.kprove-2021-10-04-15-12-45-690-aa23c232-aac8-4527-ac2d-60075b0c5226/spec.kore --spec-module STATICARRAY00-SPEC --output /home/radu/work/evm-semantics/./.kprove-2021-10-04-15-12-45-690-aa23c232-aac8-4527-ac2d-60075b0c5226/result.kore
Backend = 3.057s
Cleanup = 1.184s
Total = 25.884s
time | test |
---|---|
5.414s | functional-spec.k |
5.537s | staticarray00-spec.k |
5.795s | bytes00-spec.k |
5.274s | overflow00-nooverflow-spec.k |
5.610s | address00-spec.k |
5.632s | encodepacked-keccak01-spec.k |
5.877s | requires01-a0gt0-spec.k |
6.279s | overflow00-overflow-spec.k |
6.327s | staticloop00-a0lt10-spec.k |
6.521s | encode-keccak00-spec.k |
6.238s | keccak00-spec.k |
6.289s | requires01-a0le0-spec.k |
7.489s | infinite-gas-spec.k |
7.529s | lemmas-no-smt-spec.k |
7.853s | storagevar02-nooverflow-spec.k |
7.638s | storagevar01-spec.k |
7.893s | storagevar02-overflow-spec.k |
7.128s | storagevar00-spec.k |
6.508s | merkle-spec.k |
7.186s | lemmas-spec.k |
6.394s | storageRoot-spec.k |
6.620s | storagevar03-spec.k |
6.458s | allowance-spec.k |
5.921s | approve-failure-spec.k |
6.562s | balanceOf-spec.k |
5.954s | approve-success-spec.k |
6.210s | totalSupply-spec.k |
5.895s | transfer-failure-2-b-spec.k |
5.868s | transfer-failure-2-a-spec.k |
5.123s | transfer-failure-1-a-spec.k |
5.751s | transferFrom-failure-2-a-spec.k |
5.613s | transferFrom-failure-2-b-spec.k |
5.285s | transfer-failure-1-c-spec.k |
6.055s | transfer-success-2-spec.k |
6.233s | transferFrom-failure-2-c-spec.k |
6.276s | transferFrom-success-1-spec.k |
5.560s | allowance-spec.k |
5.562s | transfer-success-1-spec.k |
5.559s | transferFrom-success-2-spec.k |
5.976s | balanceOf-spec.k |
5.926s | approve-spec.k |
5.406s | totalSupply-spec.k |
6.148s | transfer-failure-2-spec.k |
5.413s | transferFrom-failure-2-spec.k |
5.847s | transferFrom-success-2-spec.k |
5.817s | transferFrom-success-1-spec.k |
6.054s | transfer-failure-1-spec.k |
5.520s | transferFrom-failure-1-d-spec.k |
6.190s | transfer-success-1-spec.k |
6.119s | forwardToHotWallet-failure-1-spec.k |
5.842s | transfer-success-2-spec.k |
6.566s | transferFrom-failure-1-spec.k |
6.720s | forwardToHotWallet-failure-2-spec.k |
6.708s | functional-spec.k |
6.355s | sum-to-n-spec.k |
5.843s | solidity-code-spec.md |
6.064s | forwardToHotWallet-failure-3-spec.k |
6.009s | forwardToHotWallet-failure-4-spec.k |
5.954s | transferFrom-failure-1-c-spec.k |
6.943s | cat-exhaustiveness-spec.k |
6.818s | dai-symbol-pass-spec.k |
7.100s | dai-adduu-fail-rough-spec.k |
6.715s | dstoken-approve-fail-rough-spec.k |
5.461s | transferFrom-failure-1-a-spec.k |
7.231s | dsvalue-peek-pass-rough-spec.k |
7.102s | dsvalue-read-pass-spec.k |
7.665s | flipper-tau-pass-spec.k |
7.721s | flipper-ttl-pass-spec.k |
6.835s | end-subuu-pass-spec.k |
7.656s | functional-spec.k |
5.762s | flipper-addu48u48-fail-rough-spec.k |
4.014s | evm-optimizations-spec.md |
4.936s | vat-addui-fail-rough-spec.k |
4.750s | vat-subui-pass-rough-spec.k |
4.850s | vat-subui-pass-spec.k |
4.724s | vat-mului-pass-spec.k |
4.294s | dsvalue-read-pass-summarize-spec.k |
The code links in the original post are outdated now, and don't reference anything interesting as far as I can tell.
It's about functions like this:
lazy val productionsFor: Map[KLabel, Set[Production]] =
productions
.collect({ case p if p.klabel != None => p })
.groupBy(_.klabel.get.head)
.map { case (l, ps) => (l, ps) }
It calculates the map by looking at the entire list of productions from all the imported modules.
If you take into consideration that we use these functions in visitors, then you can see the duplicated work.
A visitor will start applying a transformation on the deepest module and calculate productionsFor
. Then it's going to move to the siblings of that module and do the same thing. By looking again at the entire list of productions, which is recursive.
And so on, until the root module. So the deepest nested productions are going to be calculated x no of modules.
I haven't measured the impact of this quadratic behavior recently (which should be linear). But it might matter in certain cases, especially when comparing large objects. I think it's mostly visible when you call ParseInModule::initialize()
.
Something like this https://github.com/kframework/k/blob/master/kore/src/main/scala/org/kframework/definition/outer.scala#L246 could be optimized if we compute the results for local sentences and then combine the results with the ones from the imports.
I don't know for sure what would be the impact of such a change but we already need to initialize some of the functions to reduce work being done: https://github.com/runtimeverification/k/blob/master/kernel/src/main/java/org/kframework/parser/inner/ParseInModule.java#L138