
一台新机器上架采购清单上写着双精度峰值多少 TFLOPS同事转头问你“实际能跑多少”你打开业务程序盯了一晚上 CPU 利用率还是给不出一个数。这个场景我碰到过不止一次。Linux 下使用 Linpack 测试本质上就是回答这个问题的标准动作它用一个规模可自定、计算量几乎全压在浮点乘加上的稠密线性方程组求解任务把机器从内存通道到各级缓存再到向量单元这一整条链路压到极限最后吐出一个 GFLOPS 数字。但先说清楚一个前提——Linpack 分数不是“机器性能”的同义词它是“在特定问题规模、特定 BLAS 实现、特定进程布局下这台机器在稠密双精度 LU 分解上跑到的最好成绩”。读懂这句话后面所有参数才有意义。这篇内容写给三类人刚接手集群或高性能工作站的运维、要给新硬件做验收的工程师、以及想弄明白自己机器到底几斤几两的开发者。不需要你有并行计算背景但需要你愿意动手改配置、反复跑几轮。1. 把“峰值”和“实测”之间那道沟壑量出来很多人第一次跑 Linpack 的心情是复杂的跑之前觉得自己在测性能跑完之后觉得自己在查故障。分数往往只有纸面峰值的五成到七成如果是配置不当的机器甚至能低到两成。这个落差不是玄学它有非常明确的结构性来源。1.1 Linpack 测的是稠密 LU 分解不是你的业务代码HPLHigh Performance Linpack求解的是形如 Axb 的稠密线性方程组用的是带部分主元的 LU 分解浮点运算量约为 (2/3)·N³ 2N²性能按 (2/3)·N³ / 耗时 折算成 FLOPS。这个公式里 N 的三次方意味着什么意味着一旦 N 足够大绝大部分时间都花在矩阵乘法式的分块更新上也就是 GEMM。GEMM 恰恰是所有计算模式里对硬件最友好的那一种——数据分块后能在寄存器和 L1 里反复复用访存压力小向量指令几乎可以无间断地发射。所以 HPL 测出来的东西本质是你的机器在“算力密集、数据局部性极好”这一极端条件下的上限。它几乎不考查内存随机访问、不考查分支预测、不考查缓存 miss 带来的停顿。你的业务代码如果是图计算、稀疏矩阵、大量指针跳转或者频繁上下文切换跑出来的效率会远低于这个分数。我见过不少团队把 HPL 分数直接当作业务性能预期写进验收文档后面被业务方追着问“为什么说好几十 TFLOPS实际只有零头”根子就在这里。分数是一个参照系不是一个承诺。1.2 三种容易混淆的 Linpack 形态市面上叫“Linpack”的东西至少有三个混用会导致完全错误的结论先分清楚再动手。形态求解对象数据规模典型用途结果单位早期 LINPACK 库基准稠密方程组100×100 阶极小全在缓存里历史遗留几乎没有参考价值MFLOPSHPL稠密方程组N 可达几十万大内存受限HPC 系统排名、硬件验收、稳定性压测GFLOPS / TFLOPSHPCG稀疏方程组共轭梯度大带宽受限衡量更贴近真实应用的性能GFLOPS还有一个常被拿来凑数的是“单核 Linpack”它把 MPI 关掉、进程数设成 1测的是单个核心的极限算力。这个东西有它的用处——验证编译选项和 BLAS 是否生效特别快30 秒就能出结果——但它跟整机性能完全是两码事。我在做新机器初检时习惯先用单核跑一轮确认 BLAS 走的是优化路径、编译器选项确实进去了再去折腾多进程。这个顺序能省掉大量“改了半天参数发现根本没编译对”的时间。1.3 什么情况下值得花时间跑一次Linpack 是个重活编译、调参、反复跑时间充裕的话一套流程下来小半天就没了。它不适合作为日常监控指标也不适合用来比较不同架构的机器这点后面第 6 章会细说。我认为真正值得跑的场景有四类硬件到货验收。新机器上架跑一轮确认没有坏核、没有降频、内存通道没插错。我遇到过一次机器内存条少插了两根Linpack 分数直接掉了三成多比任何监控告警都直观。调优前后对比。换了 BLAS 库、改了编译选项、调整了 NUMA 绑定策略用同一套 hpl.dat 跑前后两轮效果一目了然。稳定性压测。HPL 会把所有核心的浮点单元长时间拉满散热不良、供电不足、内存颗粒有问题往往在跑大 N 的时候暴露出来。跑完不崩、残差校验 PASSED本身就是一种通过性验证。给业务性能定锚。先有 HPL 这个上限才能判断业务代码跑到上限的百分之多少算合理才有优化的方向感。反过来说如果只是想看看机器日常负载表现、或者想比较两台不同架构机器的“综合性能”那 Linpack 不是合适的工具跑出来的数字容易误导决策。2. 跑之前先把底层三件套配平编译不通过、跑起来段错误、分数低得离谱八成问题都出在这一层。我习惯把准备工作拆成数学库、编译与通信、系统状态三块逐块确认。2.1 BLAS 库选型这条链路的上限由它决定HPL 自己做的东西其实很有限它主要负责分块调度、进程间广播和主元交换真正的矩阵乘加全部下沉给 BLAS 的 GEMM 例程。所以 BLAS 实现的优劣几乎直接决定了分数上限。同一个 N、同一台机器换一个 BLAS分数差一倍都不夸张。常见的几类选择开源通用实现如 OpenBLAS、BLIS。优势是免费、可自编译、支持大量架构社区版针对主流 x86 和 ARM 都有手工优化的汇编内核。缺点是不同版本的性能波动明显某些版本在特定 CPU 上会有明显退步选版本时最好看一下发布说明里有没有相关的内核改动。厂商提供的优化数学库通常在安装系统时就随硬件一起提供对本厂商 CPU 的指令集和缓存层次做了深度适配一般能拿到最好的单核性能。代价是绑死平台跨架构迁移时配置要重写。参考实现Reference BLAS纯 Fortran 写成性能极低只适合验证编译链路是否通。千万别拿它跑正式分数那等于自欺欺人。判断当前用的是哪一套最直接的办法是看链接时的库路径或者跑单核 HPL 看分数——如果单核只有几个 GFLOPS 而机器理论上应该有几十上百基本可以断定落到了参考实现上。另一个快速判断是ldd ./xhpl把依赖库列出来看一眼路径指向系统默认的参考库就说明配错了。还有一个容易被忽略的点多线程 BLAS 与 MPI 进程数的冲突。如果 BLAS 库自己开了线程池而你又用 mpirun 起了 N 个进程总线程数变成 N 乘线程数核心被反复抢占性能会断崖式下跌。所以编译 HPL 时要么用单线程版 BLAS要么在运行前显式把 BLAS 的线程数环境变量设成 1。这一条我踩过当时分数只有预期的三分之一查了两天才发现是线程超订。2.2 编译器与 MPI 的版本匹配HPL 的编译依赖三样东西C 编译器、Fortran 编译器、MPI 实现。三者需要来自同一套工具链或者至少 ABI 兼容否则会出现各种诡异的链接错误或运行时崩溃。我一般遵循这么几条经验优先用系统自带的编译器和 MPI。跨版本混搭是常见的坑源尤其是在一台机器上用 A 版 MPI 编译、换到另一台机器用 B 版 MPI 运行轻则报版本不匹配重则段错误。要跨机器跑就用同一份环境镜像或者干脆在目标机器上重新编译。优化等级用 -O3指令集用 -marchnative。前者开启向量化和循环展开后者让编译器针对本机 CPU 生成最优指令。但要注意-marchnative编译出来的二进制没法在其他型号的 CPU 上运行会触发非法指令如果要在异构集群里分发就退化成具体指令集比如显式指定某个向量指令集。数学库的链接顺序很关键。链接器是从左到右解析符号的BLAS 库必须放在 HPL 目标文件之后。顺序错了会报一堆未定义符号而这个错误信息往往指向的方向完全不对让人误以为是源码缺文件。编译完成后先别急着跑多进程用单进程跑最小规模的 N1000 验证一下链路。这一步只花十几秒能过滤掉绝大部分环境问题。2.3 系统层准备频率、NUMA、大页与超线程环境配好了系统状态不对照样白搭。HPL 对这几项特别敏感CPU 频率调节策略。默认的节能调速器会在负载波动时来回变频HPL 这种持续满载的场景理论上会稳定在高频但实际观测中进入满载的爬坡过程和核心间的频率不一致仍然会吃掉几个百分点。跑分前把调速器设成性能模式是标准操作。命令是cpupower frequency-set -g performance需要 root跑完再改回来别让机器长期跑在性能模式上白烧电。NUMA 与进程绑定。多路机器上每个 CPU 插槽有自己的本地内存。如果进程被调度到了 A 插槽但访问的内存在 B 插槽每次访存都要跨互联延迟翻倍。HPL 的分块数据分布是确定的只要进程和内存都绑到同一个节点上跨节点访问量就能降到很低。绑定方式有两种用 numactl 包一层或者用 MPI 自带的绑定参数。后者更灵活我一般优先用 MPI 的参数下面第 4 章会给具体写法。透明大页。HPL 的矩阵规模动辄几十 GB页表项数量巨大TLB miss 会成为隐性成本。把透明大页设为 always 通常能带来个位数百分比的提升N 越大越明显。设置方式是往/sys/kernel/mm/transparent_hugepage/enabled写 always注意有些发行版默认是 madvise需要显式改。超线程。这条要单独强调HPL 是纯计算密集型一个物理核跑一个进程时流水线已经塞满了再塞一个逻辑核进来只会争抢执行单元和缓存。实测下来进程数设成逻辑核数也就是物理核的两倍时分数通常不升反降。所以进程数一般等于物理核数这一点和 Web 服务类负载的直觉正好相反。3. hpl.dat 里每一行对应的硬件含义hpl.dat 是 HPL 唯一的输入配置文件一共三十来行看起来吓人但真正影响分数的只有五六个数字其余基本都是保持默认即可的调优开关。第一次看到这个文件的人往往被行数劝退其实把它按功能分块读就清楚了。3.1 N、NB、P、Q 四个数的推导过程这四个数字是核心其余都是修饰。N 是问题规模也就是矩阵的阶数。它决定计算量和内存占用。N 太小通信和启动开销占比高跑不满N 太大内存放不下直接崩。经验取值是让矩阵占用可用内存的 70% 到 80%留出余量给残差计算和工作区。单个进程能分到的矩阵元素数是 N²/(P·Q)每个双精度数 8 字节所以约束是8 · N² / (P·Q) ≤ 0.8 · 单进程可用内存 N ≤ sqrt(0.8 · 单进程内存 · P · Q / 8)举个例子16 个进程每进程可用 8 GBP·Q16N ≤ sqrt(0.8 × 8 × 1024³ × 16 / 8) ≈ sqrt(1.37e10) ≈ 117000实际取 N 100000 左右比较稳妥并且要能被 NB 整除。NB 是分块大小决定每次更新时的块宽。它和 CPU 的缓存容量、向量寄存器宽度强相关。块太小GEMM 的效率上不去块太大块本身装不进缓存反而变慢。常见取值在 128 到 256 之间具体哪个最优只能扫。我的经验是先试 192 和 256 两个值如果机器缓存偏小就往下试 128、160。P 和 Q 是进程网格的两个维度必须满足 P·Q 进程总数。原则是尽量接近正方形同时让 P 略小于等于 Q。原因是进程按行优先排布时列方向的通信量更大把列方向拉长一些能减少广播次数。比如 64 个进程8×8 是最优48 个进程就取 6×8如果进程数是质数那只能 1×N通信效率会很差这也是为什么跑 HPL 时进程数尽量取合数。3.2 内存估算别让最后一个进程 OOMN 定得激进的最常见后果不是跑得慢而是跑到一半被系统杀掉。而且往往不是均匀地崩是某个进程内存先到顶被内核 OOM killer 干掉mpirun 收到一个进程退出就整体终止日志里只留一行不痛不痒的提示。稳妥的做法是跑之前先算跑的时候再盯。算的公式上面给了盯的办法是用/usr/bin/time -v mpirun ...包一层结束后会打印峰值常驻内存。如果峰值贴着物理内存上限就说明 N 该往下调一档了。还有一个隐藏开销容易漏残差校验阶段需要额外存一份向量和中间结果虽然量级是 O(N) 远小于矩阵本身但配合文件系统缓存、MPI 缓冲区实际峰值会比纯矩阵大出几个百分点。留 20% 余量不算保守。另外提醒一句内存通道数比容量更影响分数。同样容量插满所有通道和只插一半GEMM 的数据供给速度差别很大分数能差两到三成。验收新机器时如果分数莫名其妙偏低先检查内存插槽是不是插满了、有没有插错槽位导致通道不均衡。3.3 一份改两个数字就能跑的配置样例下面是 16 进程、N100000、NB192、4×4 网格的一份配置逐行注了含义。实际使用时通常只需要改 N、NB、P、Q 这几行。HPLinpack benchmark input file Local performance test HPL.out output file name (if any) 6 device out (6stdout,7stderr,file) 1 # of problems sizes (N) 100000 Ns 1 # of NBs 192 NBs 0 PMAP process mapping (0Row-,1Column-major) 1 # of process grids (P x Q) 4 Ps 4 Qs 16.0 threshold 1 # of panel fact 2 PFACTs (0left, 1Crout, 2Right) 1 # of recursive stopping criterium 4 NBMINs ( 1) 1 # of panels in recursion 2 NDIVs 1 # of recursive panel fact. 1 RFACTs (0left, 1Crout, 2Right) 1 # of broadcast 1 BCASTs (01rg,11rM,22rg,32rM,4Lng,5LnM) 1 # of lookahead depth 0 DEPTHs (0) 2 SWAP (0bin-exch,1long,2mix) 64 swapping threshold 0 L1 in (0transposed,1no-transposed) form 0 U in (0transposed,1no-transposed) form 1 Equilibration (0no,1yes) 8 memory alignment in double ( 0)几个容易改错的地方threshold那一行是残差检验的容差标准值是 16.0。有人为了让结果“通过”偷偷把它调大这等于自己骗自己跑出来的分数不具备任何参考意义。看到残差校验失败正确的做法是查配置不是改阈值。# of problems sizes (N)和下面的 N 值列表要对应。如果你写了一行数字声明有 3 个 N下面就必须列 3 个数HPL 会依次跑完输出三组结果这在批量扫描时很方便。缩进和空格不影响解析但行序绝对不能错。HPL 是按顺序读字段的少一行或者多一行注释都会导致后面全部错位报出来的错误信息还基本没有指向性。改这个文件时我习惯整段替换而不是插入行。4. 编译、启动与输出解读4.1 Makefile 里的 arch 段落HPL 的源码包里Make.Linux之类的文件名字随版本略有差异顶部有一段 arch 定义需要填四个变量arch、TOPdir、MPdir、LAdir以及编译器、链接器、编译选项、链接选项。# 编译前先定义好 arch名字随意但要和后续 make 命令一致 make archmybox对应的编译命令大致长这样mpicc -O3 -marchnative -DADD_ -I${MPI_INCLUDE} -c ${INC} ../src/*.c mpif77 -O3 -marchnative -c ../src/*.f mpif77 -o xhpl *.o ${LIB}几个细节值得说-DADD_这个宏表示 Fortran 与 C 之间符号拼接用下划线绝大多数 MPI 实现都是这个约定。如果链接阶段报一大堆形如dgemm_未定义的符号先去查这个宏有没有漏。${LIB}里放 BLAS 和 MPI 的库顺序是 HPL 目标文件 → BLAS → MPI。顺序反了会出现“库找到了但符号没解析”的怪现象。优化选项里我一般会额外加-funroll-loops和针对 Fortran 的-fno-second-underscore视 MPI 约定而定。这些不是必选项但对着编译日志试几轮能抠出几个百分点。编译产物xhpl出来后先别急着大规模跑。用 1 个进程、N1000 跑一遍mpirun -np 1 ./xhpl输出里如果能看到PASSED说明编译链路、BLAS 链接、hpl.dat 解析全部正常。这一步十几秒但能避免后面所有无效折腾。4.2 运行命令与进程绑定正式跑的时候绑定参数对分数的影响比很多人想象中大。我常用的两种写法# 写法一每个节点固定进程数绑到物理核心 mpirun -np 16 \ --bind-to core \ --map-by ppr:16:node \ ./xhpl # 写法二按插槽分布适合多路机器 mpirun -np 16 \ --bind-to core \ --map-by socket \ ./xhpl--bind-to core让每个进程固定在一个物理核上避免调度器来回迁移导致缓存失效。ppr:16:node表示每个节点起 16 个进程。如果是多路机器--map-by socket会把进程均匀撒到各个插槽配合访存的本地性效果通常更好。单机跑最简单直接mpirun -np N ./xhpl但要注意有些 MPI 实现默认会拒绝在 root 用户下运行需要额外参数豁免。这不是性能问题是安全策略别以为是环境坏了。跑的时候盯着top或者htop看一眼所有核心应该都接近 100%内存占用平稳爬升到峰值后走平。如果有核心长期低负载说明绑定或者进程数配错了。如果内存曲线呈现锯齿状反复涨落很可能是 N 太大引发了换页性能会掉得非常惨——这种时候分数没有参考价值调小 N 重跑。4.3 从 xhpl 的打印内容反推瓶颈位置HPL 的输出很简洁但信息量不小。典型输出是这样 T/V N NB P Q Time Gflops -------------------------------------------------------------------------------- WR11C2R4 100000 192 4 4 142.37 4.681e03 -------------------------------------------------------------------------------- ||Ax-b||_oo/(eps*(||A||_oo*||x||_oo||b||_oo)*N) 0.0021 ...... PASSED前六个字段是配置回显用来确认 hpl.dat 确实被正确解析了。接下来是耗时秒和折算出来的 GFLOPS。最后一行是残差校验。几个读法T/V这一列描述的是不同分块步骤所用的算法组合比如WR11C2R4大致对应面板分解用右看、递归深度、广播方式等参数的缩写。它本身不用记但如果对比两次运行时这一列变了说明配置文件被改过分数就不具备可比性。残差那一行的数值应该在 0.001 到 1 之间并且以PASSED结尾。如果看到FAILED说明解出来的 x 代回方程偏差太大可能的原因是阈值设得太小、内存布局参数比如 L1/U 转置形式和 BLAS 不匹配、或者机器本身有硬件错误。这种情况下的耗时数字直接作废不要拿去做任何对比。如果分数比预期低很多而配置看起来又没问题可以在 hpl.dat 里把# of problems sizes改成多个 N 值一次跑几个规模看分数随 N 的变化趋势。正常情况下 N 从 20000 涨到 100000分数应该明显爬升并逐渐收敛。如果分数在大 N 时反而下降基本可以断定是内存带宽或者换页的问题而不是算力问题。5. 分数上不去的排查链路这一节是我花时间最多的地方也是我认为最有价值的部分。所有结论都是踩出来的不是查文档查出来的。5.1 先分清“跑不通”和“跑得慢”这两种情况排查方向完全不同但现象经常混淆。我的判断标准很简单能出 PASSED 结果但分数低属于跑得慢中途退出、报错、卡死不返回属于跑不通。跑不通的常见原因按出现频率排现象大概率原因验证方式启动即报未定义符号链接顺序或-DADD_宏问题看链接日志里第一个未定义符号名跑几秒后某个进程退出内存不足被 OOM killer 杀掉dmesg查 OOM 记录或time -v看峰值卡在启动阶段不动MPI 进程间无法通信防火墙、主机名解析换单机模式验证再查节点间连通性跑完残差 FAILED阈值过小或内存对齐参数不匹配把 alignment 改大重试检查 BLAS 转置约定段错误在 LU 分解阶段BLAS 库版本与编译时的头文件不匹配ldd查实际加载的库路径跑得慢的原因更隐蔽因为一切看起来都正常。这种情况按下面这个顺序查命中率最高进程绑定是否正确 → BLAS 是否开了多线程超订 → 内存通道是否插满 → N 和 NB 是否合适 → 频率是否被限制。我遇到过一次非常典型的情况机器有 64 个物理核用mpirun -np 64跑分数只有同类机器的一半。top显示所有核心都在忙一切正常。最后发现 MPI 默认的进程分布策略把 64 个进程挤在了两个插槽中的一个上另一半 CPU 在空转。加上--map-by socket之后分数立刻翻倍。这个坑的隐蔽之处在于从操作系统视角看“CPU 利用率 100%”但利用率是按逻辑核算的真的全忙还是只有一半忙光看百分比看不出来。5.2 参数扫描的正确顺序调参最忌讳一上来就全组合扫描。N 有五个候选、NB 有四个候选、网格布局有三种全跑一遍就是六十次每次几分钟一天就没了而且结果还很难归因。我的顺序是这样的每一步只动一个维度第一步固定进程数和网格扫 NB。把 N 固定在一个中等规模比如内存允许的 60%P、Q 固定NB 从 128 试到 256步长 32。这一步耗时最短收益往往最大能带来 5% 到 15% 的提升。因为 NB 直接决定 GEMM 的分块效率与缓存和向量宽度最相关。第二步固定最优 NB扫 N。从小到大依次跑 40%、60%、80%、85% 内存占比对应的 N 值。观察分数是否收敛。如果 N 越大分数越高但增长趋缓说明已经接近本机极限如果大 N 反而下降说明撞到了内存或换页往回收一档。第三步扫 P×Q 布局。在进程数固定的前提下尝试不同的因数分解。比如 64 进程可以试 8×8、4×16、16×4。一般 8×8 最优但差异可能只有几个百分点属于精修。第四步微调算法参数。也就是 PFACT、RFACT、BCAST、SWAP 那几行。这些参数的影响通常在 3% 以内而且和具体 BLAS 实现相关不同机器上最优值不一样。除非前三步已经榨干了否则不建议花时间在这上面。一个很重要的提醒每次只改一个参数并且记录完整配置。我见过太多人改完 A 又改 B分数涨了却不知道是谁的功劳下次换机器就没法复现。老老实实建个表格记下来看起来笨实际上省时间。5.3 一份现象-原因对照表下面这张表是我这些年积累的覆盖了大部分“分数不对劲”的场景可以直接拿去对号入座。现象可能原因处理办法分数约为预期的 1/2 到 1/3BLAS 多线程与 MPI 进程超订把 BLAS 线程数环境变量设为 1分数约为预期的 1/4内存通道只插了一半检查插槽插满所有通道分数随 N 增大先升后降大 N 触发换页缩小 N或关闭其他占内存的服务单核分数异常低落到了参考 BLAS 实现检查链接库路径重新指定优化库多进程分数与单进程相仿进程实际跑在同一核上加--bind-to core检查绑定是否生效所有核心满载但分数偏低频率被限制在基频以下检查调速器策略和散热状态同配置两次跑差 5% 以上系统有其他负载干扰清理后台任务跑前等待系统稳定残差校验接近阈值上限精度问题可能是编译选项过于激进去掉部分浮点优化开关重编译这里单独说一个“同配置两次跑差 5% 以上”的情况。HPL 本身是确定性计算理论上一模一样的配置跑两遍结果应该几乎完全一致。如果差异明显说明有外部变量别的进程在抢 CPU、内存被其他服务占用、温度升高导致降频、甚至后台的文件同步任务占着磁盘 IO 影响了大页分配。做性能对比时环境干净比参数精确更重要。我的习惯是跑之前先确认负载为零跑完记录当时的环境温度如果两次温差超过 10 度分数差异要打个折扣看。6. 结果记录与跨机器对比的注意事项分数出来不是终点怎么用这个数字才是。这里有两个容易翻车的地方。6.1 一个分数背后必须记录的信息单独一个 GFLOPS 数字是不具备可比性的它必须和一套上下文绑定。我自己的记录模板包括这几项缺一项就认为这次测试不完整硬件配置CPU 型号的核心数、主频、内存容量与通道数、是否开启超线程。软件环境BLAS 实现及版本、MPI 实现及版本、编译器版本与优化选项。关键参数N、NB、P、Q、进程数、绑定策略。运行环境调速器策略、大页设置、是否有其他负载、环境温度。结果耗时、GFLOPS、残差校验值、是否 PASSED。峰值参照这台机器的理论双精度峰值是多少算出来的效率百分比。最后一项尤其重要。理论峰值的算法是单核每周期浮点运算次数 × 核心数 × 主频。不同架构每周期能做的 FMA 数量不同这个值得查清楚再算。有了效率百分比跨机器的对比才有意义——同样是 5 TFLOPS一台是峰值的 80%一台是峰值的 40%后者显然还有大量调优空间。6.2 换机器、换库之后的对比陷阱最容易犯的错误是拿两台架构不同的机器的绝对分数直接比较。不同架构的每周期浮点吞吐、缓存层次、内存带宽都不同绝对分数高不代表“这台机器更适合你的业务”。Linpack 分数只能说明在稠密双精度计算这一点上谁更强。第二个陷阱是换了 BLAS 版本之后直接对比。同一个库的不同版本性能可能差 20% 以上。如果两次测试之间库版本变了那分数差异里既有硬件因素也有软件因素结论就不可靠了。做对比时要么锁死软件版本只变硬件要么锁死硬件只变软件一次只动一个变量。第三个陷阱是用小规模测试的结论指导大规模配置。N20000 时最优的 NB在 N100000 时未必最优。因为规模变大后通信占比、缓存命中率、内存访问模式都会变。所以调参一定要在接近最终使用的规模上做小规模只能用来快筛。最后再分享一个我自己的习惯每次跑完正式测试把那份 hpl.dat 连同编译脚本、运行命令、结果输出一起打包存档命名带上日期和机器标识。下次换机器要对比时直接解压照着跑不用回忆当时是怎么配的。这个习惯看着啰嗦但在需要回溯“三个月前那台机器为什么分数高”的时候能救命。踩过几次“配置忘了、只能重新调一遍”的坑之后我现在的做法是没归档的测试等于没跑过。