Monad 并行执行技术原理深度解析

  • keonehd
  • 发布于 10小时前
  • 阅读 28

本文详细介绍了 Monad 如何通过乐观并行执行技术提升以太坊虚拟机的处理性能。其核心在于利用乐观并发控制(OCC)、并行发送者恢复和自定义的 MonadDB 存储引擎,在保持以太坊顺序语义一致性的同时,实现多交易并发处理和异步 I/O,显著降低了计算和 I/O 延迟。

Image

以太坊按顺序执行交易:交易 1 完成后,交易 2 才会开始。Monad 则采用乐观并行执行方式,在保持以太坊顺序语义的同时,并发完成更多工作。

并行执行的主要优势在于能够并行启动更多的状态访问。顺序执行一次只能为一个交易发布存储读取请求——只有在前一个交易完成后,下一个交易的读取才会开始。而在并行执行中,多个交易的存储读取可以同时进行,通过跨交易的异步 I/O 来隐藏延迟。计算工作也会在多个 CPU 核心上并行运行,因此不同交易的 EVM 执行、签名恢复和其他 CPU 密集型工作是重叠进行的,而不是排队等待。

这种模式直接与 Monad 的定制存储引擎 MonadDB 相结合。MonadDB 针对并发随机读取进行了优化,因此执行层生成的并行访问模式能够很好地映射到存储层,从而实现高效服务。

核心洞察在于:区块中的大多数交易并不会发生冲突。两个不同账户之间的代币转账涉及完全独立的状态;DEX 交易和 NFT 铸造也没有重叠的存储插槽。即使在没有数据依赖的情况下,顺序执行也会强制执行严格的排序,等待每个交易结束后再开始下一个。

乐观并发控制 (Optimistic Concurrency Control)

Monad 的并行执行基于乐观并发控制,这是一种源自数据库系统的成熟技术。其核心思路如下:

  1. 并发执行:假设交易之间相互独立,同时运行。
  2. 追踪读取:记录每个交易读取的内容(即读取集 Read Set)。
  3. 冲突检查:执行后,检查交易的读取集是否与之前交易的写入集(Write Set)重叠。
  4. 重新执行:如果存在冲突,则使用正确的状态重新执行受影响的交易。
  5. 按序合并:按照原始交易顺序合并状态更改。

这种方式保留了顺序执行的语义:最终状态与按顺序逐个执行交易得到的结果完全一致。

预执行:并行发送者恢复

在任何交易执行之前,所有 ECDSA 发送者签名都会并行恢复。recover_senders() 为每个交易向优先级池提交一个 Fiber,每个 Fiber 通过 recover_sender()(调用 secp256k1 ecrecover)恢复发送者地址。所有恢复工作在区块执行开始前完成。

这一点至关重要,因为 ecrecover 是每个交易中 CPU 消耗最高的操作之一。通过在区块执行前并行化这一过程,意味着该成本只需在开始前并发支付一次,且在重新执行时无需再次支付。

Fiber 基础设施

每个交易都在一个 Fiber 中运行。Fiber 是一种基于 Boost.Fibers 构建的、在线程池上多路复用的协作式协程。当 Fiber 等待 I/O 或等待前序交易时会主动让出 CPU,允许其他交易的 Fiber 在此期间运行。

交易以其索引 i 作为优先级提交到 Fiber 池,因此索引较小(较早)的交易会被优先调度。这减少了停顿时间:较早的交易更有可能处于关键路径上,从而避免阻塞后续交易的合并。

区块执行循环

execute_block_transactions 并发地将所有 N 个交易提交到 Fiber 池,然后等待它们全部完成并按顺序合并。排序由一系列 boost::fibers::promise 对象强制执行:每个交易在合并前都会等待其前序交易的 Promise,完成后设置自己的 Promise 以解除对下一个交易的阻塞。

状态架构

Monad 使用两个状态层将全局区块视图与每个交易的本地视图分开:

  • BlockState:区块的单一权威状态,由 TBB 并发哈希表支持,所有 Fiber 都可以同时读取。对于每个账户和存储插槽,它存储区块开始时的值和当前的合并值。随着交易按顺序合并,当前值会不断更新。
  • 本地 State:每个交易都在一个全新的 State 中运行,记录两类信息:从 BlockState 读取的内容(读取集)和打算写入的内容(写入集)。在合并阶段,can_merge() 会检查读取集是否仍与当前的 BlockState 一致;如果一致,则应用写入集。

此外,每个 State 都携带一个 Incarnation(区块号、交易索引对),以防止合约生命周期内的陈旧存储读取:在同一地址发生 SELFDESTRUCTre-CREATE 后,新账户将具有不同的 Incarnation,其旧存储将被忽略。

在交易内部,VersionStack 支持 EVM 调用级的回滚:子调用的写入被推入栈中,如果发生 REVERT 则丢弃,从而恢复调用前的状态。

交易执行与合并

ExecuteTransaction::operator() 在全新的本地 State 中验证并运行交易,然后等待其前序交易合并。一旦前序交易完成,can_merge() 会检查该交易读取的每个账户和存储插槽在 BlockState 中是否仍持有相同的值。

  • 如果一致,则应用写入集并解锁下一个交易。
  • 如果不一致,交易将针对当前的最新状态从头开始重新执行。由于此时前序状态已确定,重新执行保证不会再发生冲突。

为什么冲突很少见?

在典型的区块中,冲突率很低。例如:

  • 代币转账:涉及不同账户 → 无冲突。
  • 不同池的 DEX 交易:涉及不同合约存储 → 无冲突。

只有当两个交易写入同一个存储插槽时才会产生冲突(例如在同一个 DEX 池中进行两次交易,或两个铸造行为增加了同一个计数器)。即便如此,也只有较晚的交易需要重新执行,较早的交易会无条件合并。

为什么重新执行成本很低?

当发生重新执行时:

  • 签名恢复:在区块执行前已完成,无需重复。
  • 状态缓存:第一次执行期间读取的账户和存储插槽已存在于 BlockState 的并发 Map 中,因此重新执行是从内存而非数据库读取。
  • JIT 编译:如果调用的合约在第一次执行时已编译为机器码,重新执行将直接使用该编译后的代码。

实际上,重新执行的成本远低于第一次执行,因为 I/O、加密计算和编译成本已经支付过了。

与异步 I/O 的集成

当 Fiber 通过 io_uring 发起存储读取请求时会主动让出。当一个交易的 Fiber 在等待磁盘读取时,调度器会选择另一个就绪的 Fiber 运行。多个交易的 I/O 请求同时处于在途状态,SSD 并发处理这些请求。并行执行与异步 I/O 之间的这种协同作用,使得系统能够同时维持极高的 CPU 和存储利用率。

正确性保证

并行执行后的最终区块状态与顺序执行完全相同。合并通过 Promise 链按交易索引顺序发生,因此每个交易在合并时都能看到所有前序交易产生的最终状态。任何读取了陈旧数据的交易都会在合并前重新执行,且由于此时所有前序状态已定,重新执行必定成功。

结果是,Monad 区块具有与以太坊区块完全相同的语义:一组线性排序的交易,每个交易都能看到之前所有交易产生的状态。

  • 原文链接: x.com/keonehd/status/204...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
keonehd
keonehd
江湖只有他的大名,没有他的介绍。