拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Polkadot 纠删码基准测试实战:erasure-coding 的 scaling_with_validators 性能剖析

Polkadot 纠删码基准测试实战:erasure-coding 的 scaling_with_validators 性能剖析 区块链【免费下载链接】polkadotPolkadot Node Implementation项目地址https://gitcode.com/gh_mirrors/po/polkadot点击查看免费下载Polkadot 可用性availability系统的核心是每个区块的 PoVProof of Validity被纠删码切成 n 份分片交给 n 个验证者保管任何 f1 个分片即可重建完整数据。本指南以仓库 erasure-coding/benches/README.md 为主体完整讲解如何运行其唯一基准scaling_with_validators逐行拆解基准源码并结合 erasure-coding/src/lib.rs 的实现与 availability-recovery 子系统 的真实调用链带你读懂基准结果含 10_000 分片反而慢于 50_000 分片这一反直觉现象背后的算法原理。读完你将能够独立复现基准、读懂构造construct与重构reconstruct两条性能曲线并把基准结论与生产代码中的纠删码调用对应起来。一、背景为什么 Polkadot 需要分片构造 重构基准在 Polkadot 的可用性体系中每个区块的AvailableData包含 PoV 与验证数据必须被足够多的验证者持有才能保障后续的争议裁决disputes与平行链状态获取。仓库 erasure-coding/src/lib.rs 开头的文档注释给出了设计约定数据被纠删码切成 n 份并构造一棵 Merkle 树得到统一的erasure_root每个验证者只保存属于自己的那一份分片假设n 3f k0 k ≤ 3f是系统内最多故障验证者数任意 f1 个分片即可重建完整数据。也就是说可用性系统运行在一个编码一次、分片存储、少量分片即可恢复的不对称模型上。编码与解码重构都是计算密集型操作且会随验证者数量分片数变化——这正是scaling_with_validators基准存在的意义量化分片构造与 PoV 重建在不同验证者规模下的延迟与吞吐为容量规划和算法优化提供数据。二、运行基准测试仓库 erasure-coding/benches/README.md 给出了最直接的两步运行方式$ cd erasure-coding # 确保进入 erasure-coding 目录 $ cargo bench基准基于 Criterion 框架在 erasure-coding/Cargo.toml 中有完整声明[dev-dependencies] criterion { version 0.4.0, default-features false, features [cargo_bench_support] } [[bench]] name scaling_with_validators harness falseharness false表示不沿用 Rust 默认的#[test]测试入口而是由 Criterion 的criterion_main!宏接管因此cargo bench会进入该文件并执行criterion_main!(re_construct)注册的construct_and_reconstruct_5mb_pov目标。进阶运行方式由于本 crate 目前只有一个基准直接cargo bench即可。若后续加入其他基准可以精确过滤# 只运行本基准 $ cargo bench --bench scaling_with_validators # 只运行 construct 分组Criterion 支持按分组名过滤 $ cargo bench --bench scaling_with_validators -- construct # 只运行某个参数档位 $ cargo bench --bench scaling_with_validators -- construct/200Criterion 默认会生成target/criterion/目录下的 HTML 报告与回归对比图表cargo bench运行完毕后可直接打开查看。三、scaling_with_validators 基准源码逐段拆解基准文件为 erasure-coding/benches/scaling_with_validators.rs核心思想是用一份固定 5 MiB 的 PoV在 6 档验证者数量下分别测量构造分片 erasure root与仅用最少分片数重构 PoV的性能。3.1 基准负载与参数档位fn construct_and_reconstruct_5mb_pov(c: mut Criterion) { const N_VALIDATORS: [usize; 6] [200, 500, 1000, 2000, 10_000, 50_000]; const KB: usize 1024; const MB: usize 1024 * KB; let pov vec![0xfe; 5 * MB];PoV 为5 * MB5 MiB全0xfe字节流通过Throughput::Bytes(pov.len() as u64)告诉 Criterion 每次迭代处理 5 MiB从而同时给出时间与吞吐MiB/s验证者数量覆盖 200 → 50_000 共 6 档跨度 250 倍用于观察构造/重构耗时随分片数的增长曲线。3.2 被测操作封装fn chunks(n_validators: usize, pov: Vecu8) - VecVecu8 { polkadot_erasure_coding::obtain_chunks(n_validators, pov).unwrap() } fn erasure_root(n_validators: usize, pov: Vecu8) - Hash { let chunks chunks(n_validators, pov); polkadot_erasure_coding::branches(chunks).root() }obtain_chunks(n_validators, pov)对 PoV 做 Reed-Solomon 编码产出 n 个分片源码见 erasure-coding/src/lib.rs#L126-L140branches(chunks).root()把 n 个分片的哈希插入 Merkle实际为 Trie并返回根即erasure_root源码见 erasure-coding/src/lib.rs#L254-L274。3.3 construct 组编码 求根并自校验let mut group c.benchmark_group(construct); for n_validators in N_VALIDATORS { let expected_root erasure_root(n_validators, pov); group.throughput(Throughput::Bytes(pov.len() as u64)); group.bench_with_input( BenchmarkId::from_parameter(n_validators), n_validators, |b, n| { b.iter(|| { let root erasure_root(n, pov); assert_eq!(root, expected_root); }); }, ); } group.finish();要点每次迭代执行编码出 n 个分片 建 Trie 求 root这一完整操作链迭代体内的assert_eq!(root, expected_root)相当于把正确性校验放进计时循环root 必须与基准开始前预计算的期望值一致确保被测路径没有因优化或实现变更而产出错误结果BenchmarkId::from_parameter(n_validators)生成construct/200、construct/50000这样的报告名。3.4 reconstruct 组用最少分片数重建 PoVlet mut group c.benchmark_group(reconstruct); for n_validators in N_VALIDATORS { let all_chunks chunks(n_validators, pov); let mut c: Vec_ all_chunks.iter().enumerate().map(|(i, c)| (c[..], i)).collect(); let last_chunks c.split_off((c.len() - 1) * 2 / 3); group.throughput(Throughput::Bytes(pov.len() as u64)); group.bench_with_input( BenchmarkId::from_parameter(n_validators), n_validators, |b, n| { b.iter(|| { let _pov: Vecu8 polkadot_erasure_coding::reconstruct(n, last_chunks.clone()).unwrap(); }); }, ); } group.finish();要点先把 n 个分片转成([u8], usize)的(分片数据, 分片索引)对这是reconstruct的输入格式见 erasure-coding/src/lib.rs#L163-L205c.split_off((c.len() - 1) * 2 / 3)把约最后 1/3 的分片切到last_chunks。逐档核算200 档 68 片、500 档 168 片、1000 档 334 片、2000 档 668 片、10000 档 3334 片、50000 档 16668 片——恰好都等于对应recovery_thresholdf1再多 1 片因此reconstruct 组模拟的是最坏情况仅凭刚好达到恢复阈值的最少分片集重建 5 MiB PoV这比用更多冗余分片重建更能暴露解码真实开销reconstruct(n, last_chunks.clone())返回Vecu8基准用unwrap()确认重建成功若分片不足会返回Error::NotEnoughChunks。3.5 Criterion 采样配置fn criterion_config() - Criterion { Criterion::default() .sample_size(15) .warm_up_time(Duration::from_millis(200)) .measurement_time(Duration::from_secs(3)) }sample_size(15)每组只采样 15 次优先控制总时长5 MiB 级别的编码很耗时warm_up_time(200ms)极短的预热适合单次迭代本身就耗时百毫秒级的重负载基准measurement_time(3s)每个档位测量窗口 3 秒结果输出采用 Criterion 的典型格式time: [best median worst]与对应的thrpt: [worst median best]吞吐区间方向相反因为时间越小吞吐越大。四、基准结果与解读5950x 实测输出erasure-coding/benches/README.md 完整保留了在某台 AMD Ryzen 5950x 机器上运行该基准的输出原样如下construct/200 time: [93.924 ms 94.525 ms 95.214 ms] thrpt: [52.513 MiB/s 52.896 MiB/s 53.234 MiB/s] construct/500 time: [111.25 ms 111.52 ms 111.80 ms] thrpt: [44.721 MiB/s 44.837 MiB/s 44.946 MiB/s] construct/1000 time: [117.37 ms 118.28 ms 119.21 ms] thrpt: [41.941 MiB/s 42.273 MiB/s 42.601 MiB/s] construct/2000 time: [125.05 ms 125.72 ms 126.38 ms] thrpt: [39.564 MiB/s 39.772 MiB/s 39.983 MiB/s] construct/10000 time: [270.46 ms 275.11 ms 279.81 ms] thrpt: [17.869 MiB/s 18.174 MiB/s 18.487 MiB/s] construct/50000 time: [205.86 ms 209.66 ms 213.64 ms] thrpt: [23.404 MiB/s 23.848 MiB/s 24.288 MiB/s] reconstruct/200 time: [180.73 ms 184.09 ms 187.73 ms] thrpt: [26.634 MiB/s 27.160 MiB/s 27.666 MiB/s] reconstruct/500 time: [195.59 ms 198.58 ms 201.76 ms] thrpt: [24.781 MiB/s 25.179 MiB/s 25.564 MiB/s] reconstruct/1000 time: [207.92 ms 211.57 ms 215.57 ms] thrpt: [23.195 MiB/s 23.633 MiB/s 24.048 MiB/s] reconstruct/2000 time: [218.59 ms 223.68 ms 229.18 ms] thrpt: [21.817 MiB/s 22.354 MiB/s 22.874 MiB/s] reconstruct/10000 time: [496.35 ms 505.17 ms 515.42 ms] thrpt: [9.7008 MiB/s 9.8977 MiB/s 10.074 MiB/s] reconstruct/50000 time: [276.56 ms 277.53 ms 278.58 ms] thrpt: [17.948 MiB/s 18.016 MiB/s 18.079 MiB/s]用中位数整理成表时间为中位数吞吐按 5 MiB / 中位时间换算分片/验证者数construct 中位耗时construct 吞吐reconstruct 中位耗时reconstruct 吞吐20094.525 ms52.896 MiB/s184.09 ms27.160 MiB/s500111.52 ms44.837 MiB/s198.58 ms25.179 MiB/s1000118.28 ms42.273 MiB/s211.57 ms23.633 MiB/s2000125.72 ms39.772 MiB/s223.68 ms22.354 MiB/s10000275.11 ms18.174 MiB/s505.17 ms9.8977 MiB/s50000209.66 ms23.848 MiB/s277.53 ms18.016 MiB/s4.1 三个可观察的结论构造比重构快各档位下construct耗时约为reconstruct的一半。这与算法特性相符——编码生成冗余分片比解码从残缺分片恢复在 Reed-Solomon 中开销更低且重构组用的是刚好达到阈值的最少分片集属于最坏路径。规模从 200 增长到 2000耗时增长平缓构造约 94→126 ms33%、重构约 184→224 ms22%吞吐仅小幅下降。说明该区间内分片数增加带来的线性成本被 5 MiB 数据的固有编解码成本摊薄。10_000 分片反而比 50_000 分片更慢README 明确指出构造 275 ms vs 210 ms重构 505 ms vs 278 ms。README 特意点出 with10_000chunks (validators) its slower than with50_000for both construction and reconstruction这是该基准最有意思的现象。从实现看recovery_threshold与 novelpoly 的CodeParams::derive_parameters会随 n、k 选择不同的编解码参数如多项式次数与矩阵布局10_000 档位很可能落在一个对 5 MiB 负载不利的参数组合上。需要说明的是该现象仅来自本次 5950x 实测属于基准报告事实其是否可复现、是否为算法固有的参数敏感性问题需要更多机器的数据与对 novelpoly 内部实现的深入分析才能下结论不建议直接当作普适结论引用。五、底层原理从基准到 erasure-coding 实现基准调用的四个 API 全部来自 erasure-coding/src/lib.rs逐一对应实现5.1 恢复阈值 recovery_thresholdpub const fn recovery_threshold(n_validators: usize) - Resultusize, Error { if n_validators MAX_VALIDATORS { return Err(Error::TooManyValidators) } if n_validators 1 { return Err(Error::NotEnoughValidators) } let needed n_validators.saturating_sub(1) / 3; Ok(needed 1) }对应文档注释的n 3f k(n-1)/3 1即 f1是重建所需的最少分片数MAX_VALIDATORS novelpoly::f2e16::FIELD_SIZE 65536——编码基于 GF(2^16) 有限域分片数上限 65536测试field_order_is_right_size专门断言该值。5.2 编码obtain_chunkspub fn obtain_chunksT: Encode(n_validators: usize, data: T) - ResultVecVecu8, Error { let params code_params(n_validators)?; let encoded data.encode(); if encoded.is_empty() { return Err(Error::BadPayload) } let shards params .make_encoder() .encode::WrappedShard(encoded[..]) .expect(Payload non-empty, shard sizes are uniform, and validator numbers checked; qed); Ok(shards.into_iter().map(|w: WrappedShard| w.into_inner()).collect()) }流程code_params依据n_validators与恢复阈值推导 Reed-Solomon 参数CodeParams::derive_parameters对 SCALE 编码后的数据做编码输出均匀分片。5.3 求根与逐验证者证明branchespub fn branchesa, I: a(chunks: a [I]) - Branchesa, I { // 构造 trie把每个 chunk 的索引映射到其 Blake2 哈希 // ... trie.insert(encoded_index, chunk_hash.as_ref()) ... Branches { trie_storage, root, chunks, current_pos: 0 } }实现把分片索引u32编码映射到BlakeTwo256::hash(chunk)构造一棵 TrieBranches迭代器逐个产出(MerkleProof, chunk)每个验证者各得一份与自己的分片对应的 Merkle 证明branch_hash则可独立验证某个索引处的分支证明是否与 root 匹配erasure-coding/src/lib.rs#L278-L295。这就是基准中erasure_root(n, pov)的完整链路。5.4 解码reconstructreconstruct先校验输入分片索引越界、分片长度必须为偶数且一致、非空再把缺失位置补None交给params.make_encoder().reconstruct(received_shards)最终对恢复出的字节做 SCALE 解码。错误映射清晰NeedMoreShards→NotEnoughChunks、长度不一致 →NonUniformChunks等。重构输入必须携带索引正是基准里构造(chunk_data, idx)元组的原因。六、基准之外的实战印证纠删码在节点中的真实调用链scaling_with_validators测的不是孤立函数而是生产路径的真实热点。在 node/network/availability-recovery/src/lib.rsAvailability Recovery 子系统中恢复任务的阈值直接取自同一 APIthreshold: recovery_threshold(session_info.validators.len())?收到分片后先用branch_hash(params.erasure_root, chunk.proof(), chunk.index.0 as usize)验证 Merkle 证明再比对BlakeTwo256::hash(chunk.chunk)is_chunk_valid防止恶意分片昂贵计算被封装为ErasureTask枚举放到阻塞线程执行Reconstruct(n_validators, chunks, tx)内部调用reconstruct_v1Reencode(n_validators, root, data, tx)内部调用obtain_chunks_v1branches重构完成后还要求重编码并比对 rootreconstructed_data_matches_root以确认整份数据没被篡改——这与 construct 基准里迭代内assert_eq!(root, expected_root)的自校验思路完全一致。也就是说基准的每个被测片段都能在生产代码里找到对应位置分片请求reconstruct 路径与数据校验erasure root 比对正是节点每天在处理的操作。七、配套测试与模糊测试正确性兜底性能基准的前提是路径本身正确仓库为此提供了两层保障单元测试erasure-coding/src/lib.rs#L347-L418round_trip_works用 10 个验证者切分、任意取 4 片重建并断言与原数据相等roundtrip_proof_encoding对 2..16 档规模验证证明的编解码往返与branch_hash结果模糊测试erasure-coding/fuzzer/src/round_trip.rs 与 erasure-coding/fuzzer/src/reconstruct.rs前者用 honggfuzz 随机喂入 PoV 数据做 10 验证者 4 片重建往返后者随机组合(验证者数, 分片集)直接轰炸reconstruct_v1验证任何输入都不 panic、只返回确定性的错误或成功。八、复现与注意事项运行环境cargo bench需要本仓库能正常cargo build依赖 novelpoly、substrate 的 sp-core/sp-trie 等README 中的数值来自 AMD 5950x 单机实测不同 CPU、内存频率、系统负载下数值会明显不同基准输出不构成任何性能承诺解读口径时间区间取中位数吞吐按 5 MiB/次迭代计算10_000 慢于 50_000 的现象建议结合CodeParams::derive_parameters的参数选择逻辑做进一步分析后再引用延伸阅读想看分片如何被分发、存储与验证可继续阅读 erasure-coding/src/lib.rs 全量实现、availability-recovery 子系统 以及 av-store 存储子系统想自己跑模糊测试可参照 erasure-coding/fuzzer 的Cargo.toml配置。赞分享区块链【免费下载链接】polkadotPolkadot Node Implementation项目地址https://gitcode.com/gh_mirrors/po/polkadot点击查看免费下载相关推荐MinIO 纠删码Erasure Coding深度解析原理、部署实践与 Bit Rot 防护MinIO 纠删码Erasure Coding深度解析原理、部署实践与 Bit Rot 防护 MinIO 使用纠删码与校验和双重机制保护数据使其在硬件故后端存储对象存储分布式存储云原生RustFS 纠删码Erasure Coding规范解析算法、xl.meta 磁盘格式与兼容性契约RustFS 纠删码Erasure Coding规范解析算法、xl.meta 磁盘格式与兼容性契约 导读 本文以 RustFS 仓库中具有规范norma后端对象存储分布式存储突破存储性能极限RustFS纠删码基准测试全解析突破存储性能极限RustFS纠删码基准测试全解析 引言分布式存储的性能瓶颈 在分布式对象存储系统中纠删码Erasure CodingEC技术是保障数后端对象存储分布式存储上一篇快速解决Expo EAS构建失败react-native-image-picker原生依赖集成终极指南下一篇校园小情书性能优化指南提升小程序响应速度与用户体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表