“吞吐量”这个词,干网络和干系统的应该都不陌生——面试喜欢问,产品宣传喜欢写,可真到了线上排查问题的时候,你会发现理论和现实差距大到让人怀疑人生。
尤其这两天群里又在聊 XDMA 实际吞吐量很低、千兆网卡跑到 100Mbps 就上不去的案例。很多刚接触高性能网络或者 PCIe DMA 的兄弟,上来就被一堆“线速”、“包转发率”、“BDP”这些词砸懵了。吞吐量到底怎么定义?理论值怎么算?实测值为什么总是上不去?这篇文章我就从基本概念讲起,把带宽、吞吐量、实际有效速率这些关系捋清楚,再结合千兆以太网和 XDMA 这两个典型场景,聊聊瓶颈到底卡在哪、要怎么定位。
1. 吞吐量到底是什么
很多人把吞吐量和带宽混着用,这在日常聊天没什么问题,但一旦到了性能调优或者写压测报告的时候,就必须较真了。
1.1 带宽、吞吐量、速率的区别
带宽(Bandwidth)描述的是链路或设备的额定传输能力,是理论极限值。比如千兆以太网,带宽就是 1000Mbps;PCIe Gen3 x4 链路,单向带宽约 3.94GB/s。吞吐量(Throughput)描述的是在特定条件下,系统实际能传输的有效数据量,单位同样常用 bps 或 B/s。两者之间差着一大截,因为真实传输过程要付出大量额外开销:包头、确认包、重传、排队、中断处理、内存拷贝、协议栈处理……每一项都在蚕食理论带宽。
还有两个容易混淆的概念——速率和吞吐量。速率(Rate)通常指瞬时测量值,比如某一秒内传输了多少比特;吞吐量更偏向持续、稳态的平均值,通常要跑满数秒甚至更久才能得出可信结果。拿测速来说,一瞬间可能冲到 980Mbps,但持续 10 秒后稳定在 940Mbps,这个稳定值才是吞吐量。
再往下拆,吞吐量还有几个细分的观察维度:
- 网络吞吐量:物理链路端到端的有效数据传输速率
- 磁盘/存储吞吐量:存储系统持续读写数据的速率
- 总线/DMA吞吐量:数据经过总线或 DMA 引擎搬运的速率
这些维度看似不同,但核心逻辑完全一致:理论带宽扣除一切开销之后的真实有效速率,就是吞吐量。
1.2 吞吐量为什么永远到不了理论带宽
先说一个简单的例子。千兆以太网跑 TCP,很多人都能测到 940Mbps 左右,但为什么不是 1000Mbps?
以太网帧有固定开销:
- 前导码和定界符占 8 字节
- 以太网头占 14 字节
- 帧尾 FCS 占 4 字节
- 帧间隙(IFG)占 12 字节
- MTU 默认 1500 字节,TCP 负载最多 1460 字节(扣除 TCP 头 20 字节 + IP 头 20 字节)
也就是说,一个完整线速帧,在物理链路上实际占用的开销至少是 1538 字节(1500 + 14 + 4 + 12 + 8),真正传输的用户数据只有 1460 字节。有效比例大约是 1460 / 1538 = 94.9%。这样算下来,千兆以太网即使跑到线速,理论有效吞吐也只有 949Mbps。
这还只是以太网层面的开销,跑 TCP 还有三次握手、确认包、重传、拥塞控制等额外损耗。再加上网卡中断、驱动处理、系统协议栈开销,实测稳定值通常在 930~940Mbps 就很不错了——如果你测得的数值接近这个区间,说明系统状态非常健康。
注意:经常有人把 MBps 和 Mbps 混用。1 MBps = 8 Mbps,千兆网 940Mbps 换算一下是 117.5MB/s。要是看到测试工具里显示 100MB/s,不要立刻觉得有问题,先确认单位。
1.3 影响吞吐量的核心因素
把影响吞吐量的因素整理起来,大致分五类:
| 影响因素 | 说明 | 典型例子 |
|---|---|---|
| 链路物理带宽 | 硬件决定的额定极限 | 千兆网卡 vs 万兆网卡 |
| 协议开销 | 封装头、控制帧、校验 | TCP/IP 头、以太网帧头 |
| 延迟与往返时间 | 高 RTT 会严重限制吞吐 | 跨地域传输 |
| 处理能力 | CPU、内存、总线、DMA 引擎 | 软中断打满 CPU |
| 缓冲与流量控制 | 窗口大小、队列深度 | TCP 接收窗口、DMA 描述符数量 |
其中最关键也最容易踩坑的是延迟和缓冲。TCP 吞吐量有一条经典公式:
TCP 吞吐上限 ≈ 窗口大小 / 往返时延(RTT)
假设 RTT 是 10ms,接收窗口是 64KB,那么最大吞吐量就是 64KB / 10ms = 6.4MB/s,约 51Mbps。这就是为什么很多人用千兆网传跨省数据,实际吞吐低得可怜——不是链路不行,而是窗口和延迟算死了。
2. 千兆网卡实测只有 100Mbps
回到文章开头那个热搜问题:千兆以太网吞吐量只有 100Mbps。首先必须先区分一种情况——网卡协商成了百兆模式,这是最常见的低级错误。
2.1 网卡协商成了百兆模式
网卡和交换机之间通过自动协商确定工作速率。如果网线是四芯线(比如某些劣质或老旧线缆)、水晶头接触不良、交换机端口配置成强制百兆,都会导致链路协商失败或降级到 100Mbps。
排查方法很简单:
ethtool eth0看 Speed 字段,如果是 1000Mb/s 就没问题,如果显示 100Mb/s,说明物理链路就不对,先换线换口再说。
我遇到过最离谱的情况是:一整批网线看着没问题,拿测试仪测 8 芯全通,但就是协商不到千兆。最后发现是线缆用的铝芯线,高频衰减太严重,千兆根本跑不稳定。换铜芯线后立刻恢复。
2.2 协商千兆但吞吐只有 100Mbps
如果 ethtool 显示千兆,但 iperf3 实测只有 90~100Mbps,情况就复杂了。这类问题通常在以下三个层面:
第一个层面:驱动和中断处理问题
网卡收到数据包后会触发中断,如果中断频率过高,CPU 会被软中断打满,处理不过来的包只能丢弃。很多网卡驱动默认关闭了中断合并(Interrupt Coalescing),导致每个包都产生一次中断,高 PPS(每秒包数)场景下 CPU 直接成为瓶颈。
解决办法是调大合并参数。以 Intel 网卡为例:
ethtool -C eth0 rx-usecs 125 ethtool -C eth0 rx-frames 32这样把中断频率降下来,CPU 占用率能降好几个百分点,吞吐经常能提升 10%~20%。
第二个层面:CPU 亲和性和多队列
单队列网卡在高负载下很容易被打爆。如果网卡支持多队列(RSS),一定要把队列绑定到不同 CPU 核心上,否则所有队列中断都挤在一个核上,其他核闲着,性能自然上不去。
中断绑核用系统工具设置。简单做法是修改/proc/irq/下的smp_affinity列表,把各个队列中断分散到不同核心。绑完核之后,用mpstat观察各个核心的软中断占用率,如果只有一个核打满,其他核很闲,那基本就是绑核没做好。
第三个层面:PCIe 链路带宽不足
这个不常见,但确实存在。千兆网卡如果插在 PCIe 1.0 x1 插槽上,可用带宽只有 250MB/s 双向,网卡跑满 125MB/s 时看起来没问题,但如果同一总线上还挂了别的设备,或者使用了某些桥接芯片,实际可用带宽会被抢占。用lspci -vvv看一眼网卡的 LnkSta 字段,确认是不是跑在完整的链路宽度和速率上。
千兆网卡很少因为 PCIe 带宽不够而掉到 100Mbps,但一旦涉及万兆网卡或者多张千兆网卡同时跑满,PCIe 链路宽度不够导致吞吐上不去的案例并不少见。
2.3 实测吞吐量的正确姿势
用 iperf3 测吞吐量,不能上来就一条命令跑完,有几个参数必须调:
iperf3 -c 10.0.0.2 -t 30 -w 4M -l 64K -P 4-w 4M设置 TCP 窗口为 4MB,-l 64K调整 buffer 大小,-P 4用 4 条并发流。单流的情况下,受限于单核 TCP 处理能力和接收窗口,很难跑满高速链路。我见过不少 10G 环境单流只能跑 2~3Gbps,加几路并发之后轻松跑满的。
测出数值之后怎么判断正不正常?对照这张表:
| 链路 | TCP 实测参考值 | 说明 |
|---|---|---|
| 千兆以太网 | 930~940Mbps | 健康 |
| 千兆以太网 | 100~500Mbps | 需要排查 |
| 万兆以太网 | 6~9.5Gbps | 单流通常难跑满 |
| XDMA PCIe Gen3 x4 | 2500~3500 MB/s | 视传输块大小而定 |
低数值不一定代表有问题,要结合现场链路状况来判断。但如果同一个环境里别人能跑 940,你只能跑 100,那一定存在可优化的点,绝不要用“线路质量差”这个理由糊弄过去。
3. XDMA 实际吞吐量为什么很低
XDMA(Xilinx DMA)是 FPGA 和主机之间做高速数据传输的常用方案,尤其在国产 FPGA、软件定义存储、高速数据采集领域用得很多。很多人刚开始调 XDMA 的时候都挺兴奋,觉得 PCIe 带宽那么大,随便传传就是几个 GB/s,结果一测,实际吞吐低得让人怀疑 IP 核配错了。
这其实是整个系统链路共同作用的结果,不单纯是 IP 核配置的问题。
3.1 理解 XDMA 的数据路径
XDMA 本质上是一个 PCIe Endpoint DMA 引擎,它通过描述符(Descriptor)告诉 DMA 引擎“数据从哪来、到哪去、传多少”。主机驱动准备好描述符后,写寄存器通知 XDMA 启动传输;XDMA 搬运完成后,通过中断或者轮询状态位通知驱动回收描述符。
数据传输有几个关键环节,任何一个环节掉链子,都会拖累整体吞吐:
- 驱动申请内存并填充描述符
- 写寄存器触发 DMA 传输
- PCIe 总线执行 TLP 读写事务
- 中断或者轮询通知完成
- 驱动处理完成事件并回收描述符
整套流程里,有一个隐藏的公式:
有效吞吐量 = 单次传输的数据量 / (数据搬运时间 + 启动开销 + 完成开销)
启动开销和完成开销如果占比很大,哪怕传输引擎本身再快,实际吞吐也上不去。
3.2 传输块太小导致吞吐暴跌
这是 XDMA 吞吐低最普遍的原因。假设一次 DMA 传输的数据量只有 512 字节,而一次传输的启动和完成开销是 2 微秒,那么即使 PCIe 理论带宽是 8GB/s,单次传输搬运 512 字节只花了 0.064 微秒,但总耗时是 2.064 微秒,有效吞吐只有 248MB/s。
如果把单次传输块增大到 1MB,一次搬运耗时 125 微秒,总耗时 127 微秒,有效吞吐约 8.25GB/s——效率差了 30 多倍。
所以 XDMA 调优的第一原则:尽可能增大单次传输块大小。建议至少达到 64KB 以上,理想是 1MB 以上。
我见过某个项目的 XDMA 代码里,驱动每次只传输 4KB,测出来只有 500MB/s 左右,后来改成 1MB 大块传输,直接冲到 3GB/s 以上。这个优化过程几乎没有改任何硬件逻辑,只改了软件的数据组织方式。
3.3 描述符数量和内存对齐
描述符是控制 DMA 传输的灵魂。XDMA 驱动通过描述符告诉硬件每一次传输的源地址、目的地址和长度。两点值得注意:
一是描述符的深度。如果描述符队列太短,比如只有 16 个,驱动需要不停地等待硬件消费完描述符才能补充新的,经常会出现“DMA 引擎空转等活干”的情况。合理配置描述符队列深度,让它能覆盖传输过程中的所有 pending 请求,效果会好很多。
二是内存对齐。DMA 传输对内存对齐要求很高,特别是跨 4KB 页面边界的 buffer,硬件可能需要拆分成多个 TLP 处理,白白增加开销。驱动中申请 DMA buffer 时,建议按 4KB 或者更大的边界对齐,必要时用大页内存(HugePage)一步到位。
举一个对比:
| 配置项 | 低吞吐配置 | 高吞吐配置 |
|---|---|---|
| 单次传输块 | 4KB/次 | 1MB/次 |
| 描述符队列深度 | 16 | 256 |
| 内存对齐 | 不要求 | 4KB/2MB 对齐 |
| 完成通知方式 | 每个描述符中断一次 | 批量完成、中断聚合 |
很多 XDMA 驱动默认实现是“每传完一个描述符就产生一次中断”,高 PPS 场景下 CPU 直接被打满。正确的做法是开启中断聚合,或者干脆用轮询模式——在持续高速传输场景中,轮询模式配合大块传输往往比中断模式效果好得多。
3.4 实测数据和一个调优案例
我这边之前在 FPGA 板上跑过一个 XDMA 基准测试,反复调整参数后拿到了这样的数据(PCIe Gen3 x4 链路,理论 3.94GB/s 单向):
| 单次传输大小 | 实测吞吐(写方向) | CPU 占用 |
|---|---|---|
| 4KB | 480MB/s | 65% |
| 64KB | 1.8GB/s | 30% |
| 1MB | 3.2GB/s | 12% |
| 4MB | 3.4GB/s | 10% |
从 480MB/s 到 3.4GB/s,只是改了软件层的数据聚合方式和中断处理逻辑,硬件一句代码都没动。这就是“吞吐量瓶颈往往不在链路,而在系统设计”的最好注脚。
还有一个案例是写方向的性能总是比读方向高一截。这个在很多 PCIe 设备上都是正常的,原因在于读操作是“请求-完成”模式,主机发读请求后必须等设备返回数据,整个事务中间存在往返延迟;而写操作是“一锤子买卖”,只要数据送出去就完事,流水线效率能拉满。知道这个特性之后,在需要高吞吐的场景尽量设计成 DMA 写,而不是 DMA 读。
4. 吞吐量排查与调优实战
前面讲了两个具体场景,这部分把通用的排查思路和调优方法整理成一套可以照着做的方法论。因为不管是网络还是总线还是存储,底层的逻辑都是通路上的瓶颈分析。
4.1 吞吐量排查流程图(思路版)
先说排查的整体思路。吞吐量问题排查从来都不是单点问题,而是三步走的逻辑:
第一步:确认瓶颈在哪个层次
- 如果是网络:网卡→驱动→协议栈→应用
- 如果是 DMA:硬件 IP→驱动→内存→PCIe 链路
可以用排除法快速定位。跑一个本机回环测试(loopback),如果本机回环吞吐本身就低,问题大概率在协议栈或应用层;如果回环正常,再接物理链路测,重点查网卡、驱动、中断。
第二步:用工具量化关键指标
top/mpstat看 CPU 哪些核忙,软中断占比高不高ethtool -S eth0看丢包计数、队列分发情况perf top看协议栈热点和驱动热点iostat/sar -d看存储侧是否成为瓶颈- 如果是 XDMA,在驱动里加计数统计每次传输的启动到完成的平均耗时
工具数据比感觉可靠得多。不要一上来就怀疑硬件,先把系统级数据抓全,再逐层往下查。
第三步:逐个排除并验证
每改动一个参数,就重新跑一次测试,记录下来。最好绘制一张“参数变化 vs 吞吐量”的变化表,很多时候性能是多个瓶颈叠加后的结果——解决一个瓶颈,另一个瓶颈会随即暴露出来,这恰恰是性能调优的正常过程。
4.2 网络吞吐量场景的调优顺序
如果是网卡方向的问题,我一般按照以下顺序调:
- 确认链路速率和双工模式(
ethtool eth0) - 检查网卡队列数量(
ethtool -l eth0) - 调大 ring buffer 深度(
ethtool -G eth0 rx 4096 tx 4096) - 开启中断合并(
ethtool -C eth0) - 绑核并设置 RPS(Receive Packet Steering)
- 确认 MTU 设置,有条件就上 jumbo frame
- 最后才考虑协议栈相关参数(
sysctl里的tcp_rmem、tcp_wmem、net.core.rmem_max等)
这几个步骤操作成本都不高,但效果通常很显著。之前调过一个双网卡 bonding 的环境,绑定方式从主备模式改成了 802.3ad 负载均衡之后,整机吞吐从 900Mbps 提升到 1.7Gbps,这里的关键是网卡和交换机都必须在同一链路聚合模式下。
4.3 XDMA / PCIe 吞吐量场景的调优顺序
XDMA 场景特殊性更强,调优重点集中在软件怎么把数据高效喂给 DMA 引擎:
| 优先级 | 操作 | 预期效果 |
|---|---|---|
| 1 | 增大单次传输块(建议 ≥1MB) | 可能几倍提升 |
| 2 | 减小完成中断频率 | 降低 CPU 占用,提升吞吐 |
| 3 | 增加描述符队列深度 | 保证 DMA 引擎不断流 |
| 4 | 使用大页/对齐内存 | 减少拆分,提升稳定性 |
| 5 | 多通道并发传输 | 挖掘 PCIe 多队列潜力 |
| 6 | 减少寄存器读写次数 | 直接减少启动开销 |
操作的核心原则是让 DMA 引擎始终处于“有活干”的状态,同时让 CPU 的开销尽量低。这是软件视角对吞吐量的理解:你不需要提升硬件的物理上限,只需要把软件和硬件之间的配合做到严丝合缝。
4.4 一个通用的带宽—延迟理解方法
无论网络还是 PCIe,都要记住一个概念——带宽延迟积(BDP)。它表示“链路上正在飞行中的数据量上限”,等于带宽乘以延迟。
简单理解:如果链路带宽是 1Gbps,延迟是 1ms,那么链路里最多同时存在 1Mbps × 0.001s = 1Mb ≈ 125KB 的数据。如果系统窗口/缓冲只有 64KB,发送方发 64KB 之后就得停下来等待确认,链路利用率只有一半。想要跑满千兆,窗口至少要和 BDP 持平,再预留一些余量。
生活化类比:你是一条流水线,每次只能往皮带上放固定数量的零件,皮带走完一圈需要的时间是延迟。如果皮带上有 100 个零件位,你一次只能发 50 个,那另一半皮带一直空着——这就像吞吐量上不去的常见原因:有效利用的“带宽”不到物理上限的一半。
5. 常见问题速查与避坑心得
最后整理一个速查表,把吞吐量场景中最典型的问题、原因和排查方向放进一张表里,方便现场排障时参考。
| 现象 | 可能原因 | 排查/解决办法 |
|---|---|---|
| 千兆网实测只有 100Mbps 且协商百兆 | 网线劣质/四芯线/端口强制百兆 | 换线、查交换机配置 |
| 协商千兆但吞吐不到 200Mbps | 中断打满单核、驱动参数未调 | 开多队列、绑核、中断聚合 |
| 用 iperf3 单流跑不满带宽 | 窗口大小不足、单流受限 | 加-P 4并发,调-w窗口 |
| XDMA 4KB 块传输吞吐极低 | 启动开销占比过高 | 增大传输块到 1MB 以上 |
| DMA 传输中断频繁、CPU 打满 | 每描述符产生中断 | 开中断聚合或改轮询 |
| 读方向吞吐只有写方向一半 | PCIe 读事务往返延迟高 | 尽量设计成写方向传输 |
| 传输过程中掉速抖动 | 内存碎片化导致地址不连续 | 用大页/预分配缓冲池 |
再强调几个经常踩中的坑,这是常规文档里不会写但也最容易卡住人的地方:
第一,不要盲目改参数而不复测。调优是一个实证过程,不记录基线就动手,改完也不知道有没有效果。每一次调优都应该先保存当前数据,再改动一个参数,重新测试,记录结果。
第二,万兆网卡插到 PCIe 2.0 x1 插槽基本等于废了。装硬件前先用lspci -vvv看实际协商到的链路宽度和速率,不要等测试发现吞吐上不去了才回过来查槽位。
第三,万兆环境记得看 PCIe 速率上限。PCIe 2.0 x8 只有约 4GB/s 的可用带宽,跑万兆网卡满速 1250MB/s 刚好卡在极限附近,如果同时还有存储 IO 抢总线带宽,吞吐数据会很不稳定。这时候需要优先保证关键数据的通路质量。
第四,测吞吐量一定含 TCP 层的时候,要区分一个细节——UDP 测试工具(比如 iperf3 -u)的结果通常高于 TCP,但不能替代真实业务。因为真实业务几乎跑的都是 TCP 或者有确认机制的自定义协议,UDP 的“高吞吐”只是一个理论参照,不代表系统处理真实业务的能力。
我自己在实际调吞吐量问题时,最深的感受是:性能问题几乎没有一次是单一瓶颈解决之后就彻底解决的,它经常是一个瓶颈接着另一个瓶颈,层层剥开才接近真实上限。所以别指望找到一个“银弹”参数就一劳永逸。务实的做法是建立一套“数据采集 → 假设 → 验证 → 再采集”的循环,用工具量化每一步。思路对了,绝大多数吞吐量问题离解决就不远了。