
简介STREAMSimple Triad Memory Benchmark是 Linux 环境下评估内存带宽的经典基准测试工具面向系统运维、性能分析人员及中高级开发者借助 Copy、Scale、Add、Triad 四类内存操作量化数据在内存与 CPU 之间的连续传输能力为系统调优、硬件选型和缓存层次结构分析提供可靠依据。该压缩包仅 17KB共 8 个文件包含 C 与 Fortran 两种语言源码、Makefile 构建脚本以及 README 和 txt 说明文档规模精简既适合直接编译运行也便于对照源码理解测试原理或进行二次修改。目前已有 2941 人学习下载其测试结果对评估内存控制器、内存总线及缓存性能具有较高参考价值。获取后可在本地快速复现 STREAM 测试通过调整数组大小或并行度等参数比较不同硬件配置下的带宽数值从而判断内存子系统瓶颈对大数据处理、科学计算等内存敏感场景的性能优化尤具参考意义。1. Stream到底是什么为什么内存性能测试绕不开它做Linux性能调优和服务器选型的人基本都绕不开Stream这个工具。它是国际公认的内存带宽测试基准由John McCalpin博士在弗吉尼亚大学时期开发后来一直被用来评估服务器的内存子系统实际能跑到多少带宽。简单说Stream测试的不是内存“有多大”而是内存“跑多快”。它通过四种基本操作——Copy、Scale、Add、Triad——来模拟真实程序对内存的读写压力最终给出一个持续带宽数值。这个数值比内存条标签上的理论频率和带宽靠谱得多因为它考察的是整条数据通路CPU缓存、内存控制器、内存总线到实际DRAM颗粒的协同表现。在你做这些事的时候Stream几乎是必测项采购服务器时对比不同机型的内存性能尤其是HPC集群、AI训练节点、大规模数据库服务器排查应用性能瓶颈时确认内存带宽是否成为天花板超频或调优后验证内存子系统是否真的变快了评估NUMA架构下跨节点访问的代价到底有多少我当年第一次跑Stream看到结果和理论带宽差了一大截还以为是机器有问题。后来才明白能跑到理论峰值的60%到70%就已经算优秀了。这个认知偏差很多新手都会犯后面我会详细解释原因。2. Stream测试原理与四种操作模式2.1 为什么是这四种操作Stream源码里最核心的四个函数是Copy、Scale、Add、Triad。它们分别模拟了不同的内存访问模式Copy单纯从一个数组复制到另一个数组a[i] b[i]读和写各一次Scale用一个常数缩放数组a[i] q * b[i]也是读一次写一次但涉及一次乘法运算Add两个数组相加存到第三个数组a[i] b[i] c[i]读两次写一次Triad乘法加加法混合a[i] b[i] q * c[i]这是最接近真实科学计算场景的操作选这四种是因为它们的内存访问模式各不相同能覆盖绝大多数实际负载。同一个测试里如果四种操作的结果差异很大往往意味着内存子系统的某些特性有问题比如写带宽和读带宽不平衡。2.2 为什么测试结果会低于理论带宽很多人第一次跑Stream都会困惑明明内存是DDR4-3200理论带宽25.6GB/s怎么Stream只跑出16-18GB/s关键在于“持续带宽”不等于“峰值带宽”。Stream算的是在数组远超L3 Cache容量的情况下数据不断从内存进出的平均速率。这里有几个天然瓶颈内存刷新开销DRAM需要周期性刷新电荷这期间无法访问总线协议开销一次完整的内存访问需要行激活、列寻址、数据传输、预充电等多个阶段CPU前端限制即使内存足够快CPU的Load/Store单元每周期能处理的指令数也有限访存延迟虽然Stream是多线程测试但延迟仍会导致流水线停顿实际经验数据是双通道DDR4-3200的机器Stream跑出17-19GB/s属于正常四通道的Xeon平台DDR4-2933差不多能跑出60-70GB/s。能逼近理论值的80%已经说明内存子系统调校得很好了。3. 编译安装与参数选择的关键细节3.1 从源码编译的正确姿势Stream官方源码可以直接从官网下载stream.c文件编译命令看起来简单但里面的门道不少。我见过太多人直接gcc stream.c -o stream就跑结果测出来的数据只能发挥硬件一半的性能。# 下载源码 wget https://www.cs.virginia.edu/stream/FTP/Code/stream.c # 新手常见的错误编译方式性能会严重偏低 gcc stream.c -o stream # 推荐的编译方式 gcc -O3 -marchnative -fopenmp stream.c -o stream差异在哪里-O3是让编译器做最高级别的优化-marchnative会让编译器针对当前CPU的指令集生成代码。最关键的是-fopenmpStream默认是单线程运行的不加这个参数它完全发挥不出多核CPU的并行访存能力。3.2 环境变量设置与线程数选择编译完还不能直接跑需要设置OpenMP线程数和数组大小。Stream源码里默认的数组大小只有2000000个double元素对现代CPU来说太小了测出来会包含大量缓存命中数据虚高。# 设置线程数为CPU物理核心数不要用逻辑线程数 export OMP_NUM_THREADS16 # 修改stream.c里的数组大小推荐值为内存容量的1/4到1/2 # 以64GB内存的机器为例 # 编辑stream.c找到 # #define STREAM_ARRAY_SIZE 20000000 # 改为 # #define STREAM_ARRAY_SIZE 50000000数组大小的经验法则三个数组的总大小至少要是L3缓存的8到10倍这样才能确保数据在缓存里装不下被迫频繁访问内存。比如L3缓存为32MB的机器三个数组至少需要256MB以上也就是每个数组约4500万个double元素。注意Stream还会自动根据数组大小计算迭代次数你需要保证每次测试运行时间不少于10秒否则计时误差会显著影响结果。如果运行太快就继续调大数组。3.3 编译优化级别对结果的影响我实测过几组不同的编译参数对结果影响非常大编译参数说明相对性能默认无优化未优化代码编译器只做基本转换约50%-O2常见优化级别不包含可能改变程序语义的优化约65%-O3激进优化包含向量化等约80%-O3 -marchnative针对当前CPU指令集做特定优化约90%-O3 -marchnative -fopenmp多线程并行执行100%但要注意一点不要为了“好看的数据”去改代码结构或手动展开循环。Stream的意义在于对比和参考如果大家都不按标准流程来数据就失去了可比性。我通常的做法是官网代码 O3 native OpenMP然后用标准参数跑这样测试结果和别人公布的数据才有可比性。4. 运行Stream与分析测试结果4.1 完整测试流程实录我在这台双路Xeon Gold 6248R每个节点20核心40线程的服务器上做一次完整测试给大家展示具体的操作流程。# 先确认NUMA拓扑这决定了后续怎么绑定 lscpu | grep -A3 NUMA # 编译 gcc -O3 -marchnative -fopenmp stream.c -o stream # 设置线程数为物理核心总数40 export OMP_NUM_THREADS40 # 运行测试结果会直接输出到终端 ./stream运行结束后会看到类似这样的输出结构------------------------------------------------------------- Function Rate (MB/s) Avg time Min time Max time Copy 72345.6 0.0875 0.0862 0.0891 Scale 65832.4 0.0961 0.0947 0.0975 Add 72387.2 0.0961 0.0947 0.0975 Triad 77041.8 0.0902 0.0889 0.0914Rate单位是MB/s表示每秒可以搬运多少兆字节数据。看数据时重点看Min time对应的Rate因为最短时间代表最理想状态干扰最少。4.2 如何解读测试结果是否正常拿到结果后判断性能水平的经验标准对比理论带宽如果达到60%到70%是及格70%到80%是良好80%以上是优秀对比同平台的历史数据如果有明显下降超过10%优先检查内存是否降频运行、是否启用了ECC校验Copy和Triad通常是四个值中最高的两个因为现代CPU对连续读写有专门的优化路径理论带宽的计算公式很简单内存频率乘以位宽再除以8。比如DDR4-2933四通道带宽理论值 2933 MHz × 64 bit × 4通道 ÷ 8 93856 MB/s如果这台机器Stream测试只有50000 MB/s左右意味着只发挥了一半多一点需要排查是不是只插了单通道内存或者BIOS里配错了通道映射。4.3 用脚本自动化多轮测试单次测试偶然性太大建议至少跑三轮取平均值。我会写一个简单脚本处理多轮测试#!/bin/bash # stream_multi_run.sh for i in 1 2 3; do echo Round $i stream_result_$HOSTNAME.log ./stream stream_result_$HOSTNAME.log 21 done echo All tests completed at $(date) stream_result_$HOSTNAME.log多次跑还有另一个好处可以观察结果的稳定性。同一台机器每轮之间数据波动在1%以内属于正常。如果波动超过5%大概率系统存在其它负载或者CPU降频的问题。5. 进阶技巧NUMA绑定、超频验证与性能调优5.1 NUMA架构下的测试姿势现代多路服务器都是NUMA架构每个CPU访问自己本地内存快访问远端CPU的内存慢。这种场景下Stream的测试方式直接影响结果如果你把线程随便撒在40个核上有些线程会跨NUMA节点访问内存白白损失一大截带宽。# 查看NUMA节点分布 numactl --hardware # 将线程绑定在第一个NUMA节点的所有物理核上 export OMP_NUM_THREADS20 numactl --cpunodebind0 --membind0 ./stream为什要同时绑定CPU和内存--cpunodebind0是让线程只跑在节点0的核上--membind0是让内存分配只从节点0走这样能避免页面在运行时被迁移到远端节点。如果只绑CPU不绑内存操作系统可能因为内存压力把页面挪到另一个节点的内存上测试结果依然会失真。5.2 验证超频或调优是否真的有效很多人调优后只用free -h看看内存容量或者用dd去测磁盘完全没法判断内存性能变化。Stream是做这个验证的最佳工具。我做过一次DDR4从2933MHz超频到3200MHz的对比实验同样平台下配置Triad带宽提升幅度默认2933MHz61540 MB/s-BIOS开启XMP到3200MHz67020 MB/s8.9%3200MHz 收紧时序68845 MB/s11.9%这种提升在数据库和HPC场景里感知非常明显。但要注意服务器内存不像桌面内存那样适合超频稳定性优先跑完Stream之后最好再用memtest86做几个小时的压力测试确认没有ECC报错。5.3 用perf辅助定位性能瓶颈当Stream分数异常偏低时直接用perf工具看看瓶颈到底出在哪里perf stat -e cache-misses,cache-references,instructions,cycles ./stream重点看两个指标cache-misses的比例是否异常高以及cycles下每个周期执行的指令数。这两个值一个反映缓存效率一个反映CPU流水线利用率。如果cache-misses比例正常但带宽还是上不去问题大概率出在内存控制器或BIOS配置上。6. 常见问题与排查技巧实录6.1 结果不稳定每次跑差距很大这个是出现频率最高的问题。曾经遇到一台双路机器Stream跑出来的结果每轮差了15%排查了一圈发现是散热问题导致CPU温度波动温度一高CPU就降频带宽就跟着掉。排查顺序是这样先用dmesg查有没有thermal throttling的日志再用sensors看当前温度最后用cpupower frequency-info确认运行频率是否稳定。如果这些都没问题再检查后台是否有其他任务抢占CPU。另外如果系统里跑着数据库这类高内存占用服务也会干扰测试。建议测试前用top先看一眼整体负载保证系统相对空闲。6.2 Copy结果比Triad还慢感觉不对劲正常情况下Copy应该和Triad差不多或者说Triad略高。如果Copy明显低于其它三个优先怀疑是不是内存控制器的读带宽和写带宽不均衡或者某条内存通道故障导致写入性能下降。这时我会执行dmidecode -t memory查看每根内存条的配置确认所有内存通道是否插满。曾经遇到过一台机器内存条插错槽位导致双通道失效Stream Copy直接掉了30%重新插好就恢复正常了。还有一种可能系统里开了内存压缩zram/zswap会占用一部分内存带宽做压缩运算这会直接压低测试成绩。这时候需要临时关闭相关服务# 查看是否有zram设备 ls /dev/zram* # 如果有查看是swap还是zram占用的内存 swapon --show6.3 编译时报错找不到头文件新手在最小化安装的Linux系统上编译Stream会遇到stdio.h: No such file or directory这类报错。这是因为系统没装GCC配套的开发库不是Stream本身的问题。# Debian/Ubuntu系 apt install build-essential # RHEL/CentOS系 yum groupinstall Development Tools # 或 dnf groupinstall Development Tools有个容易忽略的点如果只是yum install gcc某些发行版不会自动安装libc-dev和内核头文件直接编译依然会报错。装build-essential或Development Tools这个整体组件一步到位。6.4 测试结果比官方数据差太多这个分两种情况硬件确实不行或者测试姿势不对。硬件层面的问题包括内存只插了一根、BIOS里内存频率被锁定在JEDEC标准值以下、ECC内存在做持续校验等。测试姿势方面最常见的坑是线程数设置错误。很多人用OMP_NUM_THREADS96在自己20核的CPU上跑结果操作系统在逻辑线程间来回切换性能反而下降。正确做法是设置为物理核心数不是逻辑线程数可以用这个命令确认# 物理核心数 grep cpu cores /proc/cpuinfo | head -1 # 逻辑线程数 grep processor /proc/cpuinfo | wc -l6.5 在虚拟机上跑Stream数据偏低这个问题没有太多悬念虚拟机的内存带宽天然受限因为Hypervisor层要参与地址转换和内存管理会额外消费一部分带宽。再加上云厂商普遍超售同一台物理机上的邻居也会抢内存带宽。所以Stream更适合跑在裸金属物理机上。如果一定要在虚拟机上测试建议对比同配置的物理机数据了解虚拟化损耗大概是多少而不是拿物理机标准去硬套。7. 从Stream结果到实际调优建议7.1 内存带宽不足时应用层面怎么优化如果Stream确认了内存带宽是瓶颈应用侧能做的优化有限但确实有一些实用性很强的方向第一尽量提升数据局部性。把热点数据结构压缩到缓存能装下的尺寸减少对内存的访问次数。第二用更紧凑的数据类型比如能用int32就不要用int64能压缩的字段就压缩。第三调整访问模式多使用顺序访问而不是随机访问顺序访问的内存带宽利用率远高于随机访问。7.2 BIOS层面可以调整的选项服务器BIOS里有些选项直接影响内存性能NUMA模式开启“NUMA”而不是“Flat”模式通常在HPC场景下性能更好内存交错Memory Interleaving开启后系统会把内存地址均匀分布到所有通道提升整体带宽硬件预取Hardware Prefetcher对顺序访问非常有效Stream这种测试受益明显但不是所有应用都适合开启XMP/内存频率如果服务器支持超频可以在稳定的前提下适当提升频率这些选项在不同厂商的BIOS里名称可能不同但底层逻辑是一样的。每次改动后用Stream跑一组数据改动前后对比就能验证是否真正有效。7.3 长期监控内存带宽是否下滑Stream也可以作为日常巡检的工具。我会写一个定时脚本每周跑一次并把结果记录到日志#!/bin/bash # /usr/local/bin/stream_monitor.sh DATE$(date %Y-%m-%d) HOST$(hostname) RESULT$(./stream | grep Triad | awk {print $2}) echo $DATE $HOST $RESULT /var/log/stream_history.log新增“Triad跑不到历史平均值的90%”这类指标再结合硬件预警就能尽早发现内存故障或主板老化的问题。这个方法在一批老化的机器上曾经真的帮我们提前了两周发现了内存故障避免了业务侧出现数据损坏的事故。8. 我踩过的坑和心得总结最后分享几个直接用钱买来的经验。第一永远不要只看官方公布的Stream数据就决定采购。不同测试环境、编译参数、数组大小之间的数据差异能达到30%最好自己用同一套编译参数跑完所有候选机型再对比否则极易被标称带宽误导。第二编译参数一定要记录在案。我每次测试都会把编译命令、环境变量、NUMA绑定方式都写进结果文件头部。有一次隔了两个月回头对比数据差点因为想不起当时用的什么参数而放弃幸好留了记录。第三多线程时线程数不是越大越好。超过物理核心数后性能不升反降超线程对内存密集型任务几乎没有帮助。用了OMP_NUM_THREADS等于两倍物理核心数结果Triad反而掉了5%教训深刻。第四Stream只是内存带宽测试它不能替代内存延迟测试和稳定性测试。要评估内存延迟搭配lmbench里的lat_mem_rd要测稳定性用memtest86跑一个晚上。三个工具结合才能完整覆盖内存子系统的性能画像。玩Linux越久越觉得工具只是手段读懂数据背后的硬件行为才是真正的能力。Stream看似简单用好了却能帮你把一台服务器的内存子系统摸得明明白白值得花点时间把它吃透。本文还有配套的精品资源点击获取