The source code of the benchmark has been published.
It uses the following solidity code for the “Fibonacci-100k (u128)” benchmark:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract MyFib {
function fib(uint128 n) external pure returns (uint128 b) {
if (n == 0) {
return 0;
}
uint128 a = 1;
b = 1;
for (uint128 i = 2; i < n; i++) {
uint128 c = (a + b) % 170141183460469231731687303715884105727;
a = b;
b = c;
}
return b;
}
}
Using this as the evaluation of the EVM interpreter performance comparing with Rust equivalent has number of issues:
It looks the solidity program has been compiled with optimizations disabled. I’m getting the same number of bytes (671) of the runtime bytecode compiling locally as in the bytecode used in the benchmark. The optimized version is 138 bytes shorter.
Usage of uint128 instead of uint256 makes it unnecessarily slower for no reason.
The checked arithmetic is used unnecessarily. Switching to unchecked variant removes another 181 bytes from the bytecode comparing with the optimized version.
The expression like (a + b) % 170141183460469231731687303715884105727 can be implemented with a single ADDMOD instruction.
While this is an intriguing proposal, my concerns revolve around RISC-V (also repeated by others)
It’s a general-purpose ISA with a lot of gotchas
I’ll admit that picking a general-purpose ISA comes with numerous toolchain benefits, such as being able to use LLVM with a custom backend. However, is it worth it to discard our existing toolchains? There is already work being done on compiling and transpiling the EVM to RISC-V native code, which may help us achieve the goal of faster ZK proofs. Even PolkaVM doesn’t take the ELF binary as-is. There’s the “world computer” aspect of being able to run anything Turing-complete, but the need to move funds around makes it a much tougher attack surface to audit compared to all the knowledge we’ve gained around EVM over the past ~10 years.
It is not designed for ZK proving
Folks have noted that RISC-V wasn’t designed with ZK provers in mind. It works very well compared to EVM, but it needs to be compared against approaches taken by Valida and CairoVM, which were explicitly designed for ZK, as others have noted. This means that we are likely still examining the local maxima of ZK-focused design with RISC-V.
EOF vs incremental steps
I’m not an expert on EOF but here’s my understanding. EOF already addresses many ZK proving inefficiencies by introducing static jumps, code/data separation, and function-level abstractions while maintaining ecosystem compatibility. It represents an incremental improvement already underway for the Osaka hard fork, rather than a revolutionary approach, potentially delivering significant efficiency gains with less disruption to existing toolchains and developer workflows. Are there other incremental steps also worth considering? For instance, would adding Poseidon hash function as a precompile speed up to a “good enough” point?
Time to Implement + advances in other VM technologies
Given the complexity of the project, there is a risk that, with advances in optimized ZKVMs and hardware capabilities, migrating to RISC-V could be too late, even if we start now.
(Apparently I can edit posts now), so fixed the original post.
Here’s a corollary to the first point around RISC-V complexity
I think this has been brought up too but “which” parts of RISC-V specs to take is also worth considering. I’ve been tracking hardware for the finalized vector instruction and not everyone implements it correctly. We could run into similar situation with client code too.
I think the important takeaway here is this quote:
This would be years away at best, especially with EOF just being implemented. It could be possible that risc-v is superceded by a superior ISA with better proving capabilties, or that STARK/SNARK proving EVM code becomes more computationally viable, we cannot predict future technology especially in this field where it changes every 30 seconds.
We’d also probably migrate to ZK-friendly hashes like Poseidon for the state trie (as Vitalik first said) before we even consider the feasability of starting to migrate to risc-v.
The lack of low-level optimizations is a defect from my point of view. These optimizations cannot be done at runtime because they don’t all run in linear time, they have to be done at init time. EIP-2315 retained a level of static analysis close to EOF while also supporting linear time code generation.
The numbers are totally off. I have over 1000 TPS running EVM on my laptop.
Virtually any interpreter can be made fast with JIT compilation — just look at Google Chrome, Node.js, or the JVM.
The EVM itself is not the bottleneck. It is pointless to make it faster, it is already fast. Whats the point of fast EVM if consensus is miserable and runs at 15 TPS? Not mentioned consensus being overloaded with rollup blobs that bring zero money to the network.
The real performance limit lies in the historical state database and network bandwidth. Reads and writes to the state DB take up the most time. The consensus is archaic and not competitive with other blockchains. Thats where the efforts should go, not into breaking what works well.
I understand that the ZK community is advocating for a RISC-based architecture. But why not simply compile to RISC, as they already do? If the current compiler isn’t efficient enough, improving the compiler seems like a more focused path forward.
Interestingly, switching to RISC may not help ZK at all. If gas limits are raised by 100x as Dankrad suggested, ZK systems could struggle to keep up — potentially to the point where they become unusable. So this seems like a case where the proposed change doesn’t address user needs, could degrade overall performance, and would require significant development resources that the Ethereum Foundation likely can’t afford.
Faster VMs mainly matter for EVM-compatible VMs that are not running in a ZK, or a gas-constrained environment, or with a better state database, or other applicaitons. That includes code that traverses the entire history of the chain. And even onchain it helps contracts that are not bound by state access, but by heavy crypto computation.
The way it works, more or less, is that an EVM interpreter is implemented in Rust, the Rust compiled to RISC-V, and that is what is run, slowly, by the zkVM. The comparison is to contracts written directly in Rust and compiled to RISC-V, which run much faster.
What is not tested is compiling the EVM code to RISC-V code and running that. Even simple transpilation should run faster than an interpreter, and optimizing compilers should be able to get close enough to Rust as not to matter.
So whatever other reasons there might be for changing to a RISC-V front end, performance has not been demonstrated to be one of them.
True, but given 50x prover efficiency as a goal, CPU-oriented low-level optimizations will be a big obstacle. Undoing these low-level stuff and re-optimizing for ZKP will be harder due to loss of higher level information (and loss of precision of static analysis as a consequence).
I’s say that 50x prover efficiency is a tough goal, so I believe building a pipeline which caters ZKP needs is necessary, at least, in near future.
Then I would think the RISC-V output of a Rust compiler would be a poor candidate.
(Any links to this 50X? And 50X what? The big differences between the EVM and purte Rust reported here had everything to do with interpreting vs. compiling and nothing to do with the EVM.)
As far as I can understand the first post, the idea is that RISC-V smart-contracts can be executed much faster than EVM smart-contracts executed by EVM interpreter, compiled to RISC-V code. Therefore, we can hope for much faster ZK proving of such smart-contracts too, when using RISC-V-based zkVM.
In practice, as argued above (e.g. the post), the actual workload involves cryptography and storage access (which is not present in the Fibonacci example). So, relying on RISC-V smart-contracts is not enough (to reach 50x faster proving time). Moreover, it can become a serious obstacle.
Right. And I’m saying that of course running an interpreter is going to be slower than compiling anything to RISC-V. So we should compile EVM code to RISC-V. (Or whatever language is native to the proof system.)
Not sure if I understand the question (what do you mean by “target other back ends”?), but in general our pipeline looks like this:
We use a normal, standard compiler (like rustc, clang, etc.) to build a normal, standard RISC-V ELF file. (Alternatively, we also have a Solidity compiler which uses LLVM to compile bog standard Solidity programs into a RISC-V ELF file).
We take that ELF file and relink it into our custom RISC-V-like bytecode and custom container. This happens offline (on the dev’s machine), and is the part where we apply extra optimizations to the program (like e.g. instruction fusion), simplify the semantics, and in general make it appropriate for on-chain execution.
Then the on-chain VM (which can stay simple because we do most of the work offline when we translate the raw RISC-V into our own bytecode) recompiles the bytecode into the native code and runs it.
Technically our bytecode doesn’t have to be recompiled into the native code - it could also be interpreted, or it could be even be executed to generate a ZK proof, although we haven’t implemented a ZK backend yet. We might someday, and it would be a cool project (if someone wanted to work on a ZK backend for PolkaVM I would gladly merge that PR) but we don’t really have any immediate use for it, since we can get much better and much cheaper scaling without ZK through other means. Compute-wise last time I benchmarked it I can execute the same computation from scratch something like ~20 times in the same time it’d take to verify a single ZK proof of it, nevermind needing to actually generate the proof (which is orders of magnitude more expensive), so there’s literally no point for us go the ZK route.
Technically what Ethereum could do (but won’t due to political reasons) is:
Take our PolkaVM and use it for L1 execution (it’s a general purpose VM and it’s not really specific to any particular chain). This automatically gets you wide toolchain support (you can use almost any language, not just Solidity) and blazing fast near-native execution speed for free, and we have 30+ independent implementations of the VM itself in-progress to make sure the spec is implementable independently.
Write a ZK backend for it and do whatever ZK stuff you need to do. (It should be as fast or faster than current RISC-V ZK VMs.)
Scale down on the ZK use for those parts of the system which now maybe don’t need to use ZK due to better efficiency of the base VM.
OK, I think I understand now, thanks. I think there would technical issues, not just political ones here, but I won’t try to dig into those now. The politics is so bad that after ten years we can’t even add subroutine instructions to the EVM, so I don’t expect to see the EVM change in my lifetime.