← 返回博客
2026-10-11 · 技术 · 产品页 →

三语言生物信息实现的三端重复基准:Rust / Python / R 在 8 项能力上的 N=10 测量

Linxira Bio SDK 基准报告 · 2026-10-11。在一台低端 x86 机器上对 8 项能力做三语言后端的 N=10 重复测量:环境、输入摘要、脚本、表格与图全部公开,可逐项复核。变更与声明见文末「版本与声明」。

数据状态:八项能力的 N=10 测量已在无 CI 作业的空闲窗口整窗重跑完成 (归档 bench-20261011-001;逐阶段的 CI 作业计数与 loadavg 记在 window-log.txt)。与首次(本机同时承载 CI runner)那批的对照见 §8.8:30 条逐能力/后端对照全部落在噪声带内,0 行超噪,因此没有排除任何行。 图与表随本篇发布(索引与核查方式见 §13)。

1. 摘要

我们在一台低端 x86 机器(Intel Pentium Gold G7400,2 核 4 线程,15 GiB)上,对 Linxira Bio SDK 中 8 项已实现能力做了三语言后端的重复测量:Rust 引擎,以及分别以 Biopython、Biostrings 为对照的 Python 与 R 后端。每项、每后端各计时 10 次(此前另有 1 次不计时预热), 输入与脚本固定、串行执行、powersave 调节器全程不变。

结论按两段式预注册规则给出(§6.3):

需要强调的边界:这一比较是整栈比较,但必须说清”整栈”指什么——三个臂实现同一算法, 只让语言运行时不同(同一迭代式、同一去相关顺序、同一收敛阈值),因此它测的是语言边界, 不是”Rust 对比最好的 Python 工具”(后者是另一类臂,见 §16 尾声)。 结论只在这台机器、这批输入、这套协议下成立(§11)。 还有一个范围声明必须放在摘要里:本机是 2 核/4 线程,且三端的关键路径都是单线程 (引擎当前未使用并行设施,CPython 有 GIL,R 解释器单线程),因此这轮没有体现 Rust 在并行上的 语言级优势——我们只声明测到的东西。

2. 更新日志(相对前两篇)

3. 背景

三篇脉络:①自己的毕业课题(荞麦基因组)只关心”能不能跑完”;②对一份公开 SRA 数据做了一次端到端复现, 并紧接着做了双厂商 GPU 验证;③本篇——不再挑数据、不再挑指标,按一套事先写死的协议把三语言后端放在同一批输入上跑。

动机很具体:这个 SDK 的产品承诺是”本地优先”。本地优先意味着用户很可能在没有 GPU、 内存也不宽裕的机器上跑;如果某个后端在小机器上慢 20 倍、内存吃 10 倍,那它就不是可选项。 所以我们需要的不是”Rust 比 Python 快”这种笼统断言,而是逐能力的、带区间的、可复现的对照。

4. 相关工作与第三方基准披露

本轮使用的对照臂(第三方实现,非我们编写):

后端实现基础版本
Rust本仓库引擎(crates/…)v1.1.3
Python标准库为主(csv/gzip/math)+ Biopython(FASTA 解析)+ pytrf(仅 SSR 的 C 扩展)CPython 3.14.7 / Biopython 1.88(环境实测;pack 锁钉 1.85,不一致已登记) / pytrf 1.5.0
Rbase R 为主 + Biostrings(读入与其 C 内核匹配)R 4.6.1 / Biostrings 2.80.2 / Bioconductor 3.23

对照臂的构造原则(2026-10-12 明确写出,此前只散落在 pack 源码注释里):为了让”语言边界”成为唯一变量, 三个臂实现同一算法,因此刻意不使用会替换算法的生态库——prcomp、scikit-learn、gseapy、 clusterProfiler、bio3d、Bio.PDB、phyper 均被排除,理由逐条写在各 pack 源码与能力文档中 (主因:它们的符号约定/对象模型/舍入与引擎不同,会破坏逐字段对照)。被放行的外部原生代码只有两处: 解析层(Biopython、Biostrings)与一个 C 扩展臂(sequence.ssr 的 pytrf)。 本文的数字因此回答的是”同一算法的关键路径落在解释器还是原生代码里”,不是”Rust 与最好的 Python 工具之比”; 后者是另一类臂(tool arm,如 fastp/seqkit/pyfastx),本轮未做,见 §16 尾声。

本文的任何数字都不是第三方基准套件产出的。 第三方在我们这里的身份只有两种,且都不构成”第三方套件”: ① 被编排的工具(tools/catalog.json 的 36 项里,19 个是分析工具:bcftools、samtools、bedtools、 ncbi-blast、diamond、hmmer、kraken2、salmon、minimap2、muscle、trimal、meme、iqtree、 mcscanx、snpeff、mkdssp、kaks-calculator、autodock-vina、meeko)——它们是能力的实现,不是对照臂; ② 对照臂里用到的库(Biopython、Biostrings、pytrf)——是库,不是套件。 因此本文没有 PTS/OpenBenchmarking、SPEC CPU、GIAB+hap.py 等第三方套件的任何数字;这些是我们计划中、 本轮未执行的外部锚点,见 §16 尾声。

本轮没有使用第三方基准套件。 计划中的两条外部校准路径(尚未执行,故不作为本文证据):

  1. GIAB + hap.py:用 NIST 的公开真值集校准 正确性(而非速度)。我们已把变体调用与比较能力 做到可以吃这些输入的程度,但真值集下载、区域掩码、容器化执行都未在本窗口完成。
  2. 公开托管基准平台(如 Phoronix Test Suite / OpenBenchmarking 的公开结果库): 用于把”这台机器的绝对数字”锚到第三方机器谱系上。本轮同样未执行。

我们把这两项写成”计划中”而不是”已做”,因为它们不会因为写进文章就变成已做。

5. 数据与素材

所有输入由仓库内的固定种子生成器产出(纯 Python,random.seed(42);序列类用 simulate sequence/reads 的 seed 42),因此任何读者都能在本地重建同批输入。

能力输入规模sha256
sequence.stats.v1FASTA(20 × 5 Mb)~100 MB9f8a4cb46eb11eca49ad40108648541d13d03a50a79889a90977fe505c044e78
expression.pca.v1TSV 矩阵2000 × 40273755e5eca0ae14d150365271dda778666109341ec0826baa01b7dc7bd76345
set.venn.v1TSV 集合表20k 项 × 4 集合484dffd240e46a4de6995ae935107e464185ca93c9a8025e79856e1fa5234531
enrichment.overrepresentation.v1genes.txt5k 基因84ac1b6eec0a96798aa39b905c654faa6156dc4f4af4d4eb5f672faf8d1f98db
enrichment.overrepresentation.v1associations.tsv45k 关联b523c5b4509487186461e8bc29adcdce910551ed1c4b88d4768c220c5ff9d781
structure.pdb.summary.v1PDB3000 残基 / 24k 原子ecb30a5181b376760ceab330d9244f79b28195b54a4a8cac26a8ca23e2559afe
variant.stats.v1VCF200k 变异1dab9c47535f9b3552cd3cfae1fa70ac4fccc5ff7518d4c9cf5b76950861ce53
fastq.qc.v1FASTQ200 MB / 646k reads5697241ba2c6da89e89bd860eee8b18ba370e2725e63a2b4ccda9339fed13fb9
sequence.ssr.v1FASTA~100 MB同 sequence.stats(同一 big.fa;sha256 9f8a4cb4…44e78)

生成器脚本随仓库提供(generate-inputs.py)。跨机、跨版本核对:在 Windows(Python 3.13.15) 与本次测量机(Linux,Python 3.14.7)上分别生成上表六个合成输入,六个 sha256 全部逐字节一致—— 说明这批输入不依赖生成机器,任何人都能重建。

(生成器只写临时目录;输入摘要与全部统计量随归档发布,见 §13。)

6. 方法

6.1 测量协议

6.2 统计量

中位数(median-of-10)、四分位距(IQR)、变异系数(CV)、最小/最大、峰值 RSS; 后端比值取中位数之比,并对原始 wall_ms 做 bootstrap 重采样 10,000 次(固定种子 20261011) 给出 95% 置信区间。

6.3 预注册判定规则(先定规则,再看数字)

条件结论写法
比值 CI 下界 ≥ 1.10material:可写”N× 更快(CI 下界 …)”
CI 排除 1.00 但下界 < 1.10detectable-immaterial:可分辨,但不具实际意义
CI 跨过 1.00within-noise:写”该输入与该机器上测不出可分辨差异”

规则修正记录:我们最初把”2×(两端 CV 之和)“当作噪声带阈值,实测后发现它会把一个一致的 2.4% 差异 判成噪声、把偶发的 15% 差异判成信号——离散度不是效应量。因此改用上表的两段式:可分辨性看 CI 是否排除 1.0,实质性看 CI 下界是否 ≥1.10。CV 仍逐行披露,但不再充当判定阈值。

6.4 裁剪稳健性

除全 10 次外,另按”去掉第 1 次(9 次)“与”只取中间 8 次”两种口径重算中位数与判定 (附表 S2、S3)。 三种口径下判定完全一致(16 行 0 变化);但中位数会按计时步长浮动(24 行中 14 行变化, 多为 1 个 10 ms 步长,逐行见附表 S2)——结论不依赖任何单次运行,但”中位数逐位相同”不成立。 修正 2026-10-12:初版此处写”中位数与判定完全一致”,与归档的 S2 不符。

从一次能力测量到一条判定:协议与判定规则

图:测量的完整链路——固定并哈希输入 → 1 次不计时预热 → 10 次计时重复 → 中位数/IQR/CV → bootstrap 置信区间 → 三段式判定。预热那次不进报告,裁剪口径只是稳健性检查。

7. 环境披露

项值
操作系统CachyOS(Arch 系),Linux 原生文件系统(非 drvfs 挂载)
CPUIntel Pentium Gold G7400,2 核 / 4 线程
内存15 GiB
GPU本基准不使用 GPU(另有一个独立 GPU 实验载体,不在本文范围)
Rustrustc / cargo 1.99.0
PythonCPython 3.14.7;numpy 2.5.4、Biopython 1.88、pytrf
R4.6.1;Bioconductor 3.23、Biostrings 2.80.2、jsonlite 2.0.0、digest 0.6.39
CPUPower 调节器powersave(全程不变)
存储根文件系统 btrfs / 512 GB NVMe;/tmp 是 tmpfs(7.7 GB),所有基准输入都在 RAM 上 → 测量里没有磁盘延迟项(另有 2 块机械盘存在,与本轮无关)
计时GNU time 高精度模式
引擎版本v1.1.3

这是一台低端机:2 核 4 线程、无睿频余量、powersave。它的意义不是”代表服务器”, 而是一个保守下限:在更快的机器上,解释器启动与大块内存分配等常数项会被摊薄, 比值可能因此变大或变小,所以本文的所有比值都只按本机口径声明。

8. 结果

8.1 三端中位数与判定(N=10)

完整统计量见附表 S1(§13 列出全部附表与附图)。 摘录:

能力rustpythonrpython 判定r 判定
sequence.stats.v1810 ms825 ms1 815 ms1.02× [1.01, 1.11] 可分辨,非实质2.24× [2.23, 2.30] material
expression.pca.v120 ms4 010 ms2 770 ms200× [133, 208] material138× [92.3, 140] material
set.venn.v110 ms180 ms11 075 ms18.0× [18.0, 19.0] material1108× [1090, 1128] material
enrichment.overrepresentation.v130 ms230 ms4 480 ms7.67× [7.33, 7.67] material149× [149, 157] material
structure.pdb.summary.v130 ms780 ms20 680 ms26.0× [26.0, 30.0] material689× [683, 692] material
variant.stats.v180 ms760 ms13 500 ms9.50× [9.50, 9.56] material169× [164, 174] material
fastq.qc.v11 570 ms49 345 ms702 190 ms31.4× [31.1, 31.8] material447× [444, 447] material
sequence.ssr.v11 600 ms2 280 ms2 211 405 ms1.43× [1.33, 1.45] material1382× [1290, 1391] material

(本轮另有一次 N=3 窗口的同类测量,结论一致;见 §9 与台账 docs/engine-evals/local-nas-g7400-window-2026-10-04.md。)

各后端绝对墙钟时间(线性刻度,ms)

图:各后端的绝对中位数墙钟时间(线性刻度)。同一输入、同一机器、每后端 10 次计时重复。

8.2 内存(峰值 RSS,median-of-10)

能力rustpythonr
sequence.stats.v15.9 MB69.6 MB (11.8×)321.1 MB (54.5×)
expression.pca.v18.5 MB53.4 MB (6.3×)81.9 MB (9.6×)
set.venn.v18.3 MB47.3 MB (5.7×)124.7 MB (15.1×)
enrichment.overrepresentation.v115.0 MB61.0 MB (4.1×)145.4 MB (9.7×)
structure.pdb.summary.v128.4 MB66.3 MB (2.3×)162.8 MB (5.7×)
variant.stats.v16.2 MB45.9 MB (7.4×)122.3 MB (19.8×)
fastq.qc.v16.2 MB46.0 MB (7.4×)79.4 MB (12.8×)
sequence.ssr.v121.0 MB150.7 MB (7.2×)667.5 MB (31.8×)
各后端峰值内存(线性刻度,MB)

图:各后端峰值常驻内存(median-of-10,GNU time 的 VmHWM)。

解释器后端相对 Rust 的峰值内存倍率

图:内存倍率——Python 腿 2.3×–11.8×,R 腿 5.7×–54.5×;唯一低于 2× 的行是 structure.pdb.summary 的 Python 腿(2.3×), 最高的是 sequence.stats 的 R 腿(54.5×)。

8.3 重复稳定性与比值区间

逐能力 10 次原始墙钟的散布与中位数

图:逐能力的 10 次原始 wall_ms 散布(竖线为各自中位数),用来检查”中位数不是被某一次运行拉出来的”。 多数能力的中位数在 10 次间落在 ±1 IQR 内;sequence.stats 的 rust 档位粒度是 10 ms (time 的采样分辨),因此其 CV(1.95%)主要是量化噪声而不是真实抖动——这点限制了 小绝对值的分辨力,也是我们把实质性下限放在 1.10 而不是 1.01 的原因之一。

10 次计时的相对离散度(CV)

图:重复离散度——Rust 腿有 4 项在 10 次之间逐位相同(CV 0.00%),最高 21.52%(expression.pca,中位数仅 20 ms, 量化步长在其中的占比最大);Python 腿 0.75%–8.97%,R 腿 0.08%–5.93%。离散度是稳定性证据,不参与比值判定。

比值与 bootstrap 95% 置信区间(线性刻度)

图:每对后端的比值与 95% CI;虚线为 1.00(无差异)与 1.10(实质性下限),标注给出判定。

8.4 裁剪稳健性

三种口径(全 10 / 去首 / 中间 8)的判定完全一致(16 行 0 变化);中位数会按 10 ms 计时步长浮动 (14/24 行变化,明细见附表 S2);详见 附表 S2 与附表 S3。 换言之:没有哪一条结论是靠”保留某一次运行”得来的。

8.5 记录粒度实验:一个被推翻的假设

原假设:sequence.stats 在百万级碱基上与 Python 只差 2%,是因为”每条记录才穿一次语言边界”; 那么记录越多,差距应越明显。

实验:同一约 100 Mb 的总量,三种记录形态,各 N=10。

输入形态rustpythonr
1 × 100 Mb(1 条记录)810 ms850 ms1 820 ms
20 × 5 Mb810 ms835 ms1 820 ms
100 000 × 1 kb820 ms860 ms1 930 ms

结论:假设被推翻。 记录数从 1 增到 10 万,rust 只多 10 ms、python 只多 10 ms (比值 1.037 → 1.036)。在 ~100 MB 量级上,两侧都被 I/O 与大块内存分配主导: 每碱基的活由 C 原语(str.count/str.upper 一类)承担,而每行的读取与建串两侧都要付。

同一 ~100 Mb、三种记录形态下的三端中位数

图:记录形态实验——总量不变,记录数从 1 到 10 万,三条线几乎水平,这就是原假设被推翻的直接证据。

更正后的解释(也是本文的分层依据):真正的分界线不是”记录数”,而是**“每个输入元素是否需要 解释器参与”。逐碱基逻辑落在 C 里的能力(sequence.stats)≈1×;逐项/逐记录逻辑落在解释器里 的能力(set.venn/pdb/variant/enrichment/fastq.qc)从 7.67×(enrichment 的 Python 腿) 一路到 1108×(set.venn 的 R 腿)。 小输入上出现的 ~20× 则是解释器启动约 170 ms** 落在 10 ms 级工作负载上的占比,不代表吞吐能力。

我们原本预期什么、实测推翻了什么。 最初的直觉是”数据在语言边界来回搬运”会带来可观代价—— Python 读 FASTA、把序列交给统计、再把结果传回来,多几次搬运就该慢一截。实测没有: Python 侧的热点循环并不在解释器里,而在 CPython 的 C 原语里(str.count/str.upper 这类), Biopython 的解析循环虽然逐行穿过解释器,但每行的工作量很小,而两侧都必须付的 大块内存分配与文件读取把这点差异吃掉了。所以 sequence.stats 的 1.02× 不是”Rust 没有优势”, 而是这一项上两者的关键路径都落在原生代码里——这也正是分层结论的第一层。 反过来说,一旦每元素的工作必须由解释器执行(逐碱基的 fastq.qc、逐项的 set.venn), 同一条边界就变成 31.4×–1382× 的代价(§8.6)。

8.6 分层速览

分层速览:Rust 相对两后端的墙钟比值(线性刻度)

图:分层速览——比值按层次排序,左侧是 C 库已覆盖循环的能力,右侧是解释器逐元素迭代的能力。

按判定规则排序:哪些行真的够到 1.10× 实质线

图:判定视角的阶梯——16 行中 15 行 ≥ 1.10×;唯一例外是 sequence.stats 的 Python 腿(1.02×)。 跨度达三个数量级,故纵轴分两段(1–10× 与 ≥10×)、均为线性,判定线落在下段。

层次能力pythonr
C 原语可覆盖的线性扫描sequence.stats1.02×(可分辨,非实质)2.24×
逐位置扫描(Python 侧为 pytrf,C 扩展)sequence.ssr1.43×1382×
逐碱基解释器循环fastq.qc31.4×447×
逐项解释器循环(内存内小对象)set.venn18.0×1108×
逐基因/逐变异解释器循环enrichment、variant7.67× / 9.50×149× / 169×
逐原子解释器循环structure.pdb.summary26.0×689×
三臂同算法手写幂迭代(无线性代数库)expression.pca200×138×

注意 expression.pca 一行:三个臂都是同一套手写幂迭代(500 次迭代 + Gram-Schmidt 去相关; prcomp/scikit-learn 被刻意排除,理由见 §4),所以这一行的差异不来自库,而全部来自运行时边界—— 同样的标量循环在解释器里跑(Python/R)还是在原生代码里跑(Rust)。Python 反而比 R 慢(200× vs 138×), 差别在解释器自身的循环开销与纯 Python 的逐行 csv 解析,而不是”哪个数值库更好”。 修正 2026-10-12:初版此处误写为”两侧走的都是向量化库(numpy vs prcomp)“,与实际代码不符。

8.7 正确性证据(跨后端一致性)

每一份 benchmark 报告都带三端一致性判定:全部 8 项在 1e-6 相对容差内 consistent, 精度档 high。此外做了跨机确定性核对:同一生成器产物在 NAS 与另一台机器上 sha256 逐字节一致(如 ref.fa、cell.tsv、cell.json)。 表:附表 S5。

与早期报告记录的精确度量对比(两个窗口各自记录了什么,如实并列,不混排):

校验项早期窗口 bench-20260913-001本轮(N=10)
后端一致性判定Consistent(4 项)consistent(8/8 项)
一致性容差1e-6 相对1e-6 相对(同默认)
PDB 逐字段偏差0.0(完全相同)findings 为空(无差异字段路径)
PCA / Venn 最大相对误差≤ 2.26e-11本轮改以判定口径记录:consistent,无差异字段
精度档highhigh
跨机确定性未记录是:ref.fa/cell.tsv/cell.json sha256 跨机一致

诚实说明:早期窗口额外记录了数值级的 parity 误差(如 2.26e-11),本轮记录的是 判定级证据(是否在 1e-6 内、有无差异字段)。两者不能互相替代,所以本表把两套口径 并列展示,而不是把”0.0 / 2.26e-11”挪到本轮名下。

8.8 条件对照:CI 抢核前后(同一协议、同一机器)

本机同时承载本地 CI runner。首次那批测量期间有两次作业落在 fastq.qc 的 R 腿窗口内;停用作业后按同一 协议整窗重跑(window-log.txt 逐阶段记录 CI 作业计数为 0,jobs=0)。两批逐能力、逐后端对照 (condition-comparison.tsv,30 行):

对照行有并发批干净批Δ噪声带
sequence.stats / python840 ms825 ms−1.79%±10.47%
sequence.stats / r1 840 ms1 815 ms−1.36%±20.28%
sequence.stats / rust820 ms810 ms−1.22%±4.67%
fastq.qc / r702 655 ms702 190 ms−0.07%±1.92%
条件对照:每行的位移 ÷ 自身噪声带

图:条件对照的信噪比视图——30 行的 |Δ| ÷ 噪声带全部小于 1(最大 0.26,sequence.stats 的 Rust 腿:−1.22% 对 ±4.67%), 因此 0 行超噪、无需排除任何行;原始 Δ% 与 ±带宽逐行标注。

9. 与九月窗口的对比

同能力、同命令、不同输入规模与不同窗口的对照(附表 S4,由 2026-09-13 归档生成):

能力九月窗口(N=5,小夹具)本轮(N=10,大输入)排序是否一致
sequence.stats40 / 330 / 2110 ms810 / 825 / 1 815 ms一致(rust ≤ py < r)
expression.pca40 / 300 / 460 ms20 / 4 010 / 2 770 ms一致
set.venn40 / 310 / 400 ms10 / 180 / 11 075 msr 的位置随规模剧变
structure.pdb.summary40 / 320 / 410 ms30 / 780 / 20 680 ms一致(量级随规模展开)

为什么只有四行:本表只放”两个窗口都有入库三端行”的能力(数据取自 ../statistics-comparison.tsv)。enrichment.overrepresentation、variant.stats、fastq.qc、 sequence.ssr 在九月归档里没有三端行,因此不列——把不同口径的数字并排放进来才是失实。 其中 variant.stats 目前唯一的早期数字是一次 Windows 端到端单重复(rust 42 ms / python 301 ms, 2026-09-19,无 r 行),与本轮 200k 变异的大输入不可比,仅作记录:见仓库基准记录的 2026-09-19 条目。

同能力、两个窗口:小夹具与大输入的比值对比

图:同一能力在两个窗口里的比值(small = 九月小夹具,large = 本轮大输入)。 小夹具上三端被启动/常数项拖平,比值反而更大;大输入上才显出真实吞吐。

读法:小夹具上三端都被启动/常数项拖平(几十到几百毫秒),量级差距要在大输入上才展开; 而 set.venn 的 R 腿在小夹具上看着”还行”(400 ms),到大输入直接变成 11 075 ms—— 这与 §8.5 的分层解释一致。

同能力的跨窗口绝对数字(相同输入时)中位差 <3%,说明本机测量本身是可复现的; 跨窗口数字不能直接比较的是输入规模不同的那些行(上表已标注)。

10. 工程发现:R 腿”真的跑起来”之后

三端测量真正的收获不是分数,而是把人逼到必须真的执行。R 后端此前从未在 CI 中运行过测试, 本次补上执行路径后一次性暴露 6 个缺陷,全部已修合并:

#症状根因
1SSR 能力直接报错Biostrings 只对 XStringSet 定义 width(),裸 XString 无方法
2测试字面量与断言不符测试里那个”22 bp”字面量实际是 23 个字符
3SSR 恒返回 0 条start(...) 未加命名空间前缀,命中了 stats::start(时间序列)
4上述问题长期潜伏CI 从未运行 benchmark 两个包的测试(无任何 step)
5无 SSR 的输入被 worker 拒绝空 list() 被序列化成 [],而契约要求 map({})
620k 项输入的 intersections 数量错误按 item 逐条生成,未按 occupancy mask 分组

另外顺带抓到一个产品级缺陷:looks_like_sam 仅凭”≥11 列 + 若干整数列”就把宽计数矩阵 判成 SAM(实测边界:10 列 → tsv,11 列 → sam),任何 ≥11 个样本的表达矩阵都会被 worker 拒绝。 已改为要求 SAM 的结构证据(FLAG 上界、RNAME 非纯数字、CIGAR 语法、SEQ 碱基字母)并加回归测试。

教训一句话:测试存在但不在 CI 执行 = 不存在。

11. 局限与诚实边界

12. 复现

本轮窗口的整链(暂存报告 → 输入摘要 → summary → 统计表 → 图 → 图校验)是一键脚本, 随仓库发布:archive.sh(同目录另有窗口 descriptor 与逐步脚本)。 脚本与归档位于 https://github.com/Linxira-OS/linxira-bio-sdk,下列命令均以仓库根目录为工作目录:

# 1) 生成输入(固定种子,只写 /tmp)
python scripts/benchmark/inputs/generate-inputs.py --out /tmp/lxn10   # 见台账内的种子说明

# 2) 逐能力重复测量(每后端 1 次预热 + 10 次计时)
linxira-bio benchmark run <capability> --input <file> --backends rust,python,r --repeat 10 \
  --report /tmp/lxn10/out-<capability>/report.json

# 3) 汇总为归档(校验 schema)与统计表
python scripts/benchmark/analysis/summarize-reports.py --reports-dir /tmp/lxn10 --descriptor descriptor.json \
  --output-dir benchmark-results/2026-10-11
python scripts/benchmark/analysis/statistics-tables.py --summary benchmark-results/2026-10-11/summary.json \
  --reports benchmark-results/2026-10-11 --output-dir benchmark-results/2026-10-11

# 4) 出图(自包含 SVG)
python scripts/benchmark/figures/render-svgs.py --summary benchmark-results/2026-10-11/summary.json \
  --reports benchmark-results/2026-10-11 --output benchmark-results/2026-10-11/blog-mirror/figures

13. 数据与代码可用性

附表与附图索引(正文按编号引用)

编号文件内容
附表 S1statistics.tsv每能力 × 后端的 N、中位数、IQR、CV、min/max、峰值 RSS、比值、95% CI、判定
附表 S2statistics-trimmed-medians.tsv三种裁剪口径下的中位数与 CV
附表 S3statistics-trimmed-verdicts.tsv三种裁剪口径下的比值、CI 与判定
附表 S4statistics-comparison.tsv与 2026-09-13 归档的跨窗口对照
附表 S5statistics-consistency.tsv跨后端一致性、容差、精度档
附图 F1–F12blog-mirror/figures/<lang>/方法与判定、绝对时间、内存(绝对值与倍率)、比值 CI、重复稳定性、离散度(CV)、粒度、分层速览、判定阶梯、双窗口对比、条件对照信噪比

第三方可核查方式(全部在仓库内)

你想核查什么怎么做
输入是否可复现运行 generate-inputs.py 重新生成,与归档的 inputs.sha256 逐字节比对
统计量是否由图与表一致运行 verify-figures.py:它把图上每个数字(含原始重复图的 210 个点)反解回报告里的原始 wall_ms,不一致即非零退出
图是否经过平滑/截断加工每张图注都印有”无平滑、无坐标轴截断、无统计变换”的声明;校验脚本同时检查该声明存在、且图注不超出画布
引擎当前是否用到并行grep -rn "rayon|thread::spawn|par_iter" engine/crates/ 无命中即为无并行设施(本窗口的核查结果)
重跑与条件对照是否可查归档内的 condition-comparison.tsv(30 行逐能力/后端)与 window-log.txt(逐阶段 CI 作业计数与 loadavg)
整窗如何一键复现archive.sh(暂存报告 → 输入摘要 → 归档 → 统计表 → 双语图 → 图校验)

14. 讨论与展望

14.1 讨论:分层现象说明了什么

把八项能力放在同一张纸上,能看到一个相当稳定的模式:Rust 的收益与该能力”每元素的工作落在哪一层”几乎一一对应。 当关键路径已经在 C 库或向量化数值库中完成时(sequence.stats 的逐碱基计数、expression.pca 的矩阵分解), 三端的差距会收缩到 1.0×–2.24×,其中 sequence.stats 的 Python 腿只差 1.02×(CI 下界 1.01,够不到 1.10 的实质阈值); 而当每个元素都必须经过解释器时(逐记录、逐项、逐原子、逐碱基),差距按 7.7× → 26× → 31× 展开, R 腿进一步到 149×–1382×。这不是”语言排名”,而是工作负载形状的函数:它预测的是 “这段代码把时间花在解释器里还是花在原生代码里”,而不是”这个语言好不好”。

对使用者的直接含义有三条:其一,如果你的瓶颈是线性扫描大文件,三端的差别可能真的可以忽略, 此时更该关心内存占用与启动开销(本轮 Rust 在中位内存上稳定占优,两个解释器后端分别高出 2.3×–11.8× 与 5.7×–54.5×); 其二,如果瓶颈是”每元素都要做一点判断”的循环(质控、集合运算、注释解析、变异遍历), 差距会直接变成一到三个数量级;其三,expression.pca 这一行常被误读成”库的质量掩盖了语言差异”,必须纠正:三个臂用的是 同一套手写幂迭代(刻意不用 prcomp/scikit-learn,以保住逐字节对照),所以 200× 与 138× 量的是”解释器标量循环 vs 原生标量循环”。真正的”库质量掩盖语言差异”证据在同轮另一行: sequence.ssr 的 Python 臂换成 C 扩展 pytrf 后,差距从 31× 级塌到 1.43×。

14.2 展望:多核、GPU,与虚拟细胞

本轮最重要的结论之一是范围结论:我们只测到单核,而 Rust 在并行上的语言级优势恰恰尚未登场。 把没测到的当作优势来写是不诚实的,所以下一步的计划在这里明确。

多核是第一优先。 当前引擎的 CPU 密集路径没有使用任何并行设施,压缩输入的解压与计算也没有重叠—— 这意味着在 TB 级 gzip 数据上,它与原生 Python/R 的差距会被 I/O 与解压吃掉(Python 的 gzip 同样是单线程, 这一项目前无法拉开)。已确定的落地方向是:并行解压(BGZF 分块读、普通 gzip 走 SIMD 解压库)、 解压与计算流水线重叠、以 Rayon 承担多核计算、以 Tokio 编排海量文件与进程的 IO 并发。 本轮的 1.02×–1382× 是单核数字;并行落地后,超大序列比对与多轨迹模拟的差距应由核数继续放大。 届时的测量会在同一协议之上扩展:加入核数—加速比曲线(含并行效率与拐点), 并把每个测点的线程数固定在报告里,否则读者无法区分收益来自算法还是来自核数。

各家在”Rust 写加速器”上的官方进度并不一样(本文按 2026-10-12 复核,出处见 §17)。 先分清两件常被 混为一谈的事:“Rust 能调用 GPU”与”Rust 能编写内核”。四家厂商在这两件事上位置完全不同。

我们自己的执行记录同样要分清:已经跑通的是”一份 C/C++ 内核源码、两家原生工具链”——同一份内核套件 (copy/triad/直方图/Pearson)分别用 nvcc(RTX 4080S,sm_89)与 mcc(MTT S4000)编译, median-of-3、逐项正确性门全过,两卡在各内核上的比值可复算(§17 出处)。Rust 内核那一步我们尚未跑通: cutile-rs 当时不在 crates.io、依赖链与驱动门槛使其未进入执行;摩尔线程的 cudarc-musa 官方 Rust 调用层已经存在、但我们从未实测(我们的 gpu-musa 后端目前仍是 stub)。所以”一份 Rust 源码、 两家厂商”是待验证路线,已完成的结论是单源内核的可移植性——剩下的是工具链成熟度,不是架构问题。

虚拟细胞为什么两者都需要。 我们实现的机制型细胞模拟(Gillespie 精确法、转录爆发、Monod 生长、 二项分裂)当前同样是单线程的。这类模拟的并行轴天然存在:不同轨迹、不同参数点、不同细胞彼此独立, 属于典型的”令人尴尬的并行”;一旦要做参数扫描或群体级模拟,核数直接决定可研究的规模。 再往上,把组学层面的计算接入(这正是”AI 虚拟细胞”方向真正吃算力的地方)会让 GPU 成为必需品, 而 Rust 现在在两家厂商的官方 GPU 路线上都有了入口。

这些数字的定位。 本窗口的单核数字将成为并行版本的对照基线:同一台机器、同一批输入、同一套协议, 才能把”并行带来的收益”与”语言/实现差异”分开报告。并行实现将按版本共存的方式推进并重新走完整验收 (契约、夹具、技能文档、双语文档),本文不承诺时间表。

15. 常见问题与回答

Q1:Rust 的领先是不是只因为对手是解释器? 不完全是,而且这个说法本身被数据反驳:sequence.stats 只差 1.02×(CI [1.01, 1.11] 不含 1.00,可分辨但低于 1.10 的实质阈值), 因为该能力的逐碱基逻辑在 C 原语里完成;解释器代价只出现在”每个元素都必须过解释器”的能力上。 换句话说,优势来自工作负载形状,而不是来自”对手是解释器”这一事实本身。

Q2:对照臂是否写得不够优化,从而放大了差距? 先纠正一个容易误解的说法:对照臂不是”各语言的惯用库写法”,而是同一算法在三种语言下的实现 (parity arm),刻意排除会替换算法的生态库(§4)。所以它们既没有”故意写慢”,也不是”Python 的最优解”: 它们量的是语言边界。若换成各语言的标准工具(fastp/seqkit/pyfastx/scikit-learn),比值会显著变化—— 这正是 §16 尾声要做的事。我们也不主张”Python 慢”这一笼统说法。

Q3:只测单核,是否等于没有体现 Rust 的真实优势? 是,而且这一点已写入摘要与 §11:引擎当前不含任何并行设施,CPython 受 GIL 约束,R 的解释器也是单线程; 在 2 核/4 线程的机器上,并行优势在物理上就不可能体现。我们不把未测量项计为优势,多核列为后续第一优先。

Q4:10 次重复是否足以支撑结论? 判定规则在测量前固定(区间排除 1.00 才称可分辨;区间下界 ≥1.10 才称 material),三种裁剪口径结论一致, 每后端 10 条原始墙钟全部公开;而唯一不构成实质差距的那一项,我们就写”可分辨但不计为优势”。这本身就是统计纪律的证据。

Q5:为什么只用一台机器、一种输入规模? 单机单规模是本窗口刻意设定的范围;输入规模为 100–200 MB,远高于启动开销的量级。 跨机器的锚定(公开托管基准平台)与正确性真值(GIAB 与 hap.py)列为计划中,未执行前不作为证据。

Q6:R 腿在 SSR 上每次约 37 分钟,是否刻意选择了最慢的库? 该腿使用 Biostrings 的 matchPattern,是这一生态的标准做法,其代价随输入与模体空间增长; 我们如实给出绝对耗时(N=10 中位数 2 211 405 ms,约 36.9 min/次),并说明这是算法特性而非调参结果。 同一能力上 Python 侧用的是 pytrf(C 扩展,不是纯 Python),所以 Python 腿只差 1.43×—— 这恰好说明本文是”整栈比较”:与 C 扩展比差距小,与 R 的逐位置解释器循环比差距是三个数量级。

Q7:内存对比是否公平? 使用同一工具(GNU /usr/bin/time -v 的 VmHWM)、同一时刻定义、取 10 次中位数; 并明确披露常驻集大小不等于全部内存占用(共享库与映射文件的归属方式不同)。

Q8:为什么没有使用第三方基准套件? 本轮对照臂是第三方库实现,不是第三方基准套件,这两件事不能混为一谈;GIAB 与 hap.py(正确性真值)、 公开托管基准平台(机器谱系锚定)为计划中,未执行前不作为证据(§4、§11)。

Q9:如何确认图上的数字与表格一致,而不是被”调”过的? 图与表由同一脚本从同一份归档生成;复核脚本会把图上每个数字(含原始重复图的 210 个点)反解回归档中的数值, 不一致即报错;每张图注均印有”无平滑、无坐标轴截断、无统计变换”的声明,坐标轴一律线性且截断范围直接写在图上。 核查方式见 §13。

16. 尾声:明天的第二轮——把”同一算法”换成”各语言的标准做法”

写完这一轮,我们把自己的三个对照臂源码逐行重读了一遍,发现一件必须写在明处的事,它也决定了下一步。

本届三个臂是”同一算法 × 三种语言”,不是”三种语言的惯用写法”。 为了让变量收敛到运行时边界, 三个臂实现同一个算法(同一迭代式、同一 Gram-Schmidt 去相关顺序、同一收敛阈值),并刻意排除 会替换算法的生态库(prcomp、scikit-learn、gseapy、clusterProfiler、bio3d、phyper;理由见 §4)。 唯一放行的外部原生代码是解析层(Biopython/Biostrings)与一个 C 扩展臂(sequence.ssr 的 pytrf)。

由此可以说清本文数字的边界:它回答**“同一算法的关键路径落在解释器还是原生代码里”**, 而不是”Rust 对比最好的 Python 工具”。反证就在本文内部:sequence.ssr 的 Python 臂换成 C 扩展 pytrf 后,差距从 31× 级塌到 1.43×。

明天(2026-10-13)我们会按更标准的代码再跑一轮,把”各语言/生态的标准做法”作为第二类臂(tool arm): expression.pca 用 scikit-learn 与 prcomp,sequence.stats 用 pyfastx/seqkit, fastq.qc 用 fastp 与 pyfastx,其余能力尽量换成生态公认实现,并逐项标注算法是否等价。

两类臂分开测量、分表呈现、绝不混排:

同一轮里我们还会补上第三方锚点(此前一直是”计划中、未执行”,这次明确排期): ① 压缩对照用 lzbench 与 Squash(单线程参考 + 固定迭代语义); ② 引用 PTS 的 compress-gzip/compress-zstd/compress-7zip profile(带 profile 版本与运行命令) 并把本轮结果上传 OpenBenchmarking 做机器谱系锚定(附 PTS 自身的可比性警告:同版本、同硬件、同日); ③ 正确性真值用 GIAB HG002 + hap.py,与速度声明分开陈述。 注意 PTS 至今没有 bgzip profile,所以”第三方套件里已有的生信/压缩项”能对上的只有一部分—— 对不上的部分我们只能如实说明,而不是拿别的负载冒充。

协议不变(固定输入与 sha256、1 次不计时预热 + N=10 计时、中位数 + bootstrap CI、两段式判定、 裁剪稳健性、条件对照),因此两轮数字可直接对照——但两类臂之间不可混排比较。 这是本轮最重要的一条自我更正:先把自己写的是什么弄清楚,再谈谁快。

同一轮还要修三处”锁与环境不一致”(重读代码时发现,属可复现性缺陷而非数字错误): ① Python pack 的 requirements.lock 锁 biopython==1.85/numpy==2.2.4,而归档环境实测为 Biopython 1.88 / numpy 2.5.4 —— 锁没有约束住实际运行的环境,下一轮统一; ② expression_pca_v1.py 的 docstring 曾声称 numpy.linalg 参与运算(实际未用),已更正; ③ 对照臂在路线图里被写成”生态对标工具”,与实际的 parity 臂互斥,已改为两类臂通则(§7.1)。

17. 参考文献

  1. NVIDIA. CUDA Rust platform(cuda-oxide、cutile-rs 与运行时 crate):https://github.com/NVIDIA/cuda-rust; 官方公告:《Introducing CUDA Rust: Two Tracks for Writing GPU Kernels》,2026-09-08: https://developer.nvidia.com/blog/introducing-cuda-rust-two-tracks-for-writing-gpu-kernels
  2. Intel. openvino-rs(官方推理绑定,覆盖 CPU/GPU/NPU):https://github.com/intel/openvino-rs; oneapi-rs(sycl-rs,实验性;内核为 C++ SYCL 源串):https://github.com/oneapi-src/oneapi-rs; ittapi(插桩绑定):https://github.com/intel/ittapi
  3. AMD. ROCm 官方组织下的 Rust 仓库:https://github.com/ROCm/spur、https://github.com/ROCm/rocm-cli、 https://github.com/ROCm/amdsmi(含官方 rust-interface/);第三方 Rust 内核路径:CubeCL: https://github.com/tracel-ai/cubecl
  4. 摩尔线程. cudarc-musa(官方维护 fork;feature musa-50200 = MUSA 5.2 Driver ABI): https://github.com/MooreThreads/cudarc-musa;sppark-musa(crate sppark 的 musa feature): https://github.com/MooreThreads/sppark-musa
  5. 本仓库的双厂内核实测(同一份 C/C++ 内核,nvcc 与 mcc 两条原生工具链,RTX 4080S 与 MTT S4000): https://github.com/Linxira-OS/linxira-bio-sdk/blob/main/docs/engine-evals/gpu-rental-round3-dual-vendor-2026-10-02.md
  6. 本文对厂商 Rust 支持状态的复核台账(2026-10-12;含四家三层矩阵、检索方法学与开发方向 §8): https://github.com/Linxira-OS/linxira-bio-sdk/blob/main/docs/engine-evals/vendor-rust-status-2026-10-12.md
  7. 本文引用的全部软件版本见 §7 环境披露;Biopython、Biostrings、numpy、R 与 CPython 均为各自项目发布版本。

18. 版本与声明


Linxira-OS · AGPL-3.0-or-later(代码)· 本文 CC-BY-4.0 · https://github.com/Linxira-OS/linxira-bio-sdk