性能三问 —— 「快 6 倍」与语言无关
系列gdev-masterNVIDIA/nouveau 用户态 GPGPU 运行时从 C/C 到 Rust 的移植工程上一篇主问题「能」已答完但留了三个方面问题①有数据为零的情况②分析性能提升 6 倍原因③多次测试验证。本篇回答。先展示结论数值口径同门语言差十相位 median 比值全部 1.00Total 117.636 vs 117.036差 0.5%区间交叠user_test 原物、n512、每臂 100 发、交替先后手median下位中数、min–max 随行加码轮 200 发含百发轮 100 发同批续算楔洞全部回补后无一缺失跨门栈差Init0.79/ MemAlloc0.32/ HtoD0.87/ DtoH1.36/ Close2.27五段真不交叠同一套工装、20C20UC内核门gdev.koU用户门libdrm/nouveau即 docx 原配置Q1「为零」C 侧同样量到 0 ⇒计时粒度噪声非 bug同段两侧 median 0.000、区间交叠Q3「Test passed」两侧全矩阵校验同 FAIL⇒ 该字样不含数值信息错在 cubin 层、与用哪个库无关插针版全矩阵校验cab 逐元素 两侧 dump 落盘比对一、一张表与三个问题那张表单位 ms同一 user_test 二进制唯一变量LD_LIBRARY_PATH阶段CRustRust/CInit133.482.40.62HtoD0.0020.237118×Exec0.5180.0850.16「6 倍」出处DtoH0.0220.1798.1×Close0.8522.1822.6×DataRead0.0010.000000—Total134.985.10.63上机前先做源码级静态核对C 是 oracle一切回到源码单位与规模单位是毫秒madd.c:282不带参数跑是n3main.c:8默认——3×3 矩阵、36 字节、grid(1,1) 共 9 线程。除 Init/Total 外每一段测的几乎全是 API/驱动的固定开销不是算力。为零的那段根本不经过库DataRead 段计时只包住 9 次dummy_c c[idx]madd.c:237-242——读了不比、扔掉。36 字节刚被 DtoH 写进 L19 次读不到 1µs计时器gettimeofday粒度就是 1µs。0.000000 区分不了「快」和「没执行」——18 篇的老朋友看得见的形状不是性质。方法三债单次无重复先 C 后 Rust页缓存/GPU 已热的顺序效应嫌疑输出直打 SSH 终端库打印走 stderr混进终端 I/O。「快 6 倍」就是在这三个债上只挑了一行 Exec 得出的。二、先回答问题这两遍到底在比什么性能对照的第一件事不是计时是搞清楚变量。原表中那轮其实是这样的C 臂: user_test → libgdev(内核门薄壳) → gdev.ko → ioctl → GPU Rust 臂: user_test → libgdev(用户门整库) → libdrm/nouveau → GPU两条不同的驱动路——一边是 2012 年 gdev 自己的内核模块一边是发行版 libdrm 用户态栈。这叫跨门走的门不同。它比出来的任何差都归不了「语言」只能归「两套栈」。要问语言得让两边走同一扇门。于是有了 P5lib/build/gdev_lib.c这 547 行内核门薄壳原本在移植范围之外用户裁决「Rust 要像替换零件一样无缝融入原 C走 C 同款门范围不足就改」——翻案补译28 个导出、featurekernel-gate、与 C 薄壳同 SONAME 同符号面。从此 Rust 库也能走 gdev.ko同门对照成立同一门里唯一变量才是「C 写的还是 Rust 写的」。纪律跟着立起来在拿到同门数据之前一切性能差异的归因口径禁写「语言」一律写「两套栈」悬置待验。三、判据先行性能轮的 J0–J3「让检查会失败、且失败在该失败的地方」搬到性能上就是四道门判据内容防的是J0 指纹四库 sha256 逐个对登记值对不上停尺子被换J1 非退化LD_DEBUGlibs证 R 臂真加载 gdev-rust-stage、C 臂真加载基线 build「绿」来自回落到同一份 C 库的假绿J2 正确性插针版全矩阵校验cabmismatches 全报 两侧GDEV_DUMP落盘cmp「Test passed」那种无数值的形状J3 口径交替先后手奇数对 C 先、偶数对 R 先 输出全重定向 只准按 median 讲、min/max 必随行顺序效应、终端 I/O、单次噪声插针版还把 Init 拆成四段cuInit/Ctx/ModLoad/FuncConf、Exec 拆两段、Close 拆两段——docx 里那个「-51ms 的 Init 优势」终于可以被切开看。四、六轮收口从方向到大样本轮规模结果轻强度参考10 发单发跨门显著差全部消失或缩到噪声级——第一个方向信号madd 原版124 发n3 口径 77另 n16 单对十项指标区间全交叠Exec 同臂自身抖 15×单发噪声主导memcpy4 对两侧 median 同为 (12,13)µs——带宽级拷贝同速S2 正式 n5123 对×两二进制跨门时代的 121×/8.6×/2.6× 在 1MB 负载下全消失百发轮100 发50C50R十相位 median 比值全 1.00加码轮200 发100C100R含百发轮 100 发同批续算同左两个独立 50v50 批与合并 100v100 同判批间无漂移发数口径加码轮统计的 200 百发轮 r001–r100 新增 r101–r200同一 runner 同判据续算入仓全绿原始样本合计 240 份 100 100 40。加码轮的完整表每臂 100median下位中数括号内 min–maxms阶段C_medmin–maxR_medmin–maxR/C交叠Init111.208108.583–136.810110.732108.497–137.3741.00是MemAlloc0.2210.209–0.3040.2210.209–0.2561.00是DataInit2.0621.621–2.3072.0561.681–2.2891.00是HtoD1.4731.464–1.4911.4711.464–1.4821.00是KernConf0.0020.001–0.0030.0040.003–0.0082.00*是Exec0.4210.120–0.9840.3790.121–1.0500.90是DtoH0.6740.640–0.7160.6740.651–0.7101.00是DataRead0.5300.522–0.6210.5290.521–0.5421.00是Close1.0130.977–1.1541.0090.970–1.1481.00是Total117.636114.714–143.478117.036114.571–143.9330.99是*KernConf 绝对差 2µs计时粒度Exec 0.90 但两臂各自跨 8.2×/8.7×区间交叠噪声主导不构成显著差。批间互证构成它的两个独立 50v50 批——首轮Total 117.101 vs 117.183差 0.07%与扩展批十相位 R/C 0.99–1.04——单独统计亦全部交叠批间无漂移。五、X1「两套栈差」同门把语言面闭了但「两套栈到底差多少」还缺一份正式档数字——§3.3 的跨门轮量过 n3 微基准121× 一类那是固定开销的量纲。X1 轮补上 1MB 真负载档同一套工装、每臂 20 发、n512C内核门 vs U用户门docx 原配置阶段C_medmin–maxU_medmin–maxU/C交叠Init110.695108.739–136.90587.39982.044–103.2960.79否MemAlloc0.2220.211–0.2360.0720.071–0.0880.32否DataInit2.0941.743–2.3412.2271.865–2.4551.06是HtoD1.4741.467–1.4991.2791.265–1.2860.87否KernConf0.0020.002–0.0030.0030.002–0.0041.5是Exec0.2970.129–0.8840.1260.123–0.3320.42是DtoH0.6750.665–0.7120.9170.830–1.0301.36否DataRead0.5270.522–0.5480.5300.523–0.5431.01是Close1.0250.995–1.1742.3232.268–2.6222.27否Total117.280114.841–143.29494.76789.687–110.9560.81否三个读法每条路各有便宜的段用户门 Init/MemAlloc/HtoD 快0.79/0.32/0.87内核门 DtoH/Close 快1.36/2.27。没有谁全面赢——「哪套栈好」是个分段函数不是单值。docx 的 118× 是负载变形n3 时用户门每次 HtoD 有约 0.24ms 固定开销压死 36 字节拷贝118×到 1MB 后固定开销摊薄、比的是拷贝吞吐用户门反而快 0.87。同一个 API两种量纲两个方向——这就是单次微基准的欺骗性。Total 0.81 的「用户门快」全由 Init 驱动cuCtxCreate 段一项 -23ms与 docx 的 0.63 是同一个故事。把 Init 扣掉两侧合计只差毫秒级。六、答案①「快 6 倍」——问题不是语言是两条栈同门 100v100 十相位全 1.00跨门的 6×/118×/8× 全部归两条驱动栈且幅度随负载变形118× 甚至反转。语言面零差。十行逐行接住——原表两列照抄右边是两个正式口径下的复测比值阶段原表 C原表 Rust原表比值同门 R/C跨门 U/CInit133.44782.3590.621.000.79MemAlloc0.1000.0580.581.000.32DataInit0.0010.0011.001.001.06HtoD0.0020.237118×1.000.87KernConf0.0020.0031.52.00*1.5Exec0.5180.0850.16「6×」出处0.900.42DtoH0.0220.1798.1×1.001.36DataRead0.0010.000000—1.001.01Close0.8522.1822.6×1.002.27Total134.94685.1040.630.990.81-口径原表单次、跨门、n336 字节微基准-同门 R/C每臂 100 发、n512-跨门 U/C每臂20 发、n512。*KernConf 绝对差 2µs计时粒度Exec 同门 0.90 但区间交叠噪声主导DataRead行的落差原表 的 0 vs 复测的 0.53是负载与粒度所致见 ②。一句话读法每一个「显著差」在同门列全部归一换到跨门列、换了量纲后依然在——只是幅度、方向可能变了。②「为零」——纪律正确零是粒度同门情况下C 侧同样量到 0证明与移植无关且这段不经过被测库同一二进制的纯用户态读回循环两臂同代码——n3 下只有 9 次读、不到 1µs量出来就是粒度舍入。到 n512同一段遍历 1MB≈26 万元素两侧 0.530/0.529 同速非零。③「多测多验」——本篇的全部结构就是这句话交替先后手、median 口径、多样本——不相信单次看到的化。对照示例同门测试-C同门测试-Rust这一篇的一句话性能对照的第一个判据不是计时是搞清楚在比什么。「快 6 倍」属于两条驱动栈不属于两种语言——拿看得见的形状当看不见的性质在性能维度又现形了一次这次有 240 份全绿样本把它钉死。