第3章 吞吐率概念:数据传输速率与效率

咱们做嵌入式开发的,天天跟“速率”打交道。SPI 标称能跑 50 Mbps,可实际用起来,连一半都跑不满。问题出在哪?说白了,你看到的“速率”只是物理层的一个上限,真正的数据传输效率,得看吞吐率。

我习惯把吞吐率叫作“能干活的速度”。物理层速率是路标,吞吐率才是你车实际跑上去的速度。中间差了多远?嗯,这章我们就把它掰开揉碎了讲清楚。

3.1 吞吐率不只是数字

先给个定义:吞吐率 是指单位时间内成功传输的有效数据量。单位常用 Mbps 或 MB/s。

注意“有效”二字。你发 100 个字节,里面可能夹着 30 个控制字段、校验码、帧头帧尾。真正对应用有用的,可能只有 70 个字节。这种开销,在计算吞吐率时全得扣掉。

我曾经帮一个同事排查 SPI 显示刷屏慢的问题。他查了 SPI 时钟频率是 20 MHz,算下来理论速率 20 Mbps,可屏幕刷新率死活上不去。后来一测,他每帧数据里塞了 30% 的帧同步头和冗余填充。吞吐率实际只有 14 Mbps 左右。这就是典型“看数字不看结构”的坑。

3.2 两个关键公式

咱们先把数学摆出来。虽然简单,但我建议你每次做性能评估前,都手写一遍这两条式子。

// 公式一:理论峰值吞吐率
Peak = 物理层时钟频率 × 数据线宽度 × (单位时间内时钟沿数)

// 公式二:实际有效吞吐率
Effective = (有效数据量) / (传输总时间)

// 更实用的形式:
Effective = 
  (总传输数据量 × 有效数据占比)
  ------------------------------------
  预传输耗时 + 传输耗时 + 后处理耗时

第二个公式里的分母,才是决定实际吞吐率的关键。预传输耗时包括:DMA 请求延迟、CPU 对 SPI 外设的配置时间、以及缓冲区分配时间。传输耗时就是实际 SPI 收发的时间。后处理耗时包括中断响应、数据搬移、状态清理。

我在一个项目里遇到过 DMA 请求延迟导致死锁的例子。那个系统里,DMA 每次传输前要等 12 个时钟周期的仲裁和地址映射。12 个周期看起来不多,但每 64 字节就要重复一次。算下来,有效吞吐率直接掉了 18%。

我的小技巧:做性能建模时,先把“请求延迟”加成一个独立的项。我用逻辑分析仪抓过很多次 SPI 波形,发现实际传输的间隔往往比预想的大。那些间隔里藏着的就是 DREQ 到 DACK 的等待、总线仲裁、以及寄存器读写。

3.3 开销藏在哪里?

你要想提升吞吐率,得先知道开销在哪。我总结了三个大户:

  • 协议开销:帧头、帧尾、CRC、控制字节。SPI 本身是字节流,但上层协议(比如 LCD 命令帧、SPI Flash 页编程)常常每发 256 字节就要塞一次命令+地址。这部分开销占比可以在 5% ~ 30% 浮动。
  • DMA 请求延迟:从外设产生 DMA 请求到 DMA 控制器实际搬移第一个字节的时间。包括:DMA 仲裁等待(多通道竞争)、源/目标地址准备、以及总线对齐停顿。每次请求的延迟虽然只有几到几十个时钟,但如果传输粒度很小(比如每次 32 字节),延迟占比就会很可观。
  • 软件栈开销:驱动层函数调用、中断上下文切换、信号量或互斥锁的加解锁。这些在实时系统里尤其容易忽略。我曾经在一个 FreeRTOS 项目里发现 SPI 发送任务因为等待 DMA 完成信号量,每帧多等了 200 µs。你看,光这 200 µs 就让 2 Mbps 的物理链路实际只能跑 1.2 Mbps。

举个例子,一个典型的 SPI Flash 页写过程:

阶段 耗时 (µs) 说明
发送写使能命令 3.2 8 位命令 + 8 位 dummy
等待 DMA 请求 0.8 DMA 仲裁 + 地址准备
传输 256 字节数据 20.48 SPI 时钟 100 MHz,单线
后处理(状态查询) 5.0 轮询忙标志
总计 29.48 有效数据占比 = 256 / (总耗时×带宽率) ≈ 87%

你看,DMA 请求延迟仅 0.8 µs,但加上后处理,有效吞吐率就被拉低了 13%。如果传输粒度更小(比如每次 64 字节),DMA 请求延迟占比会更大。

注意:不要只看百分比。时间也很重要。我曾经遇到过 DMA 请求延迟在低优先级任务里被无限阻塞,导致 SPI 传输超时。这种情况在资源紧张的多通道系统里特别容易发生,尤其当 DMA 通道和更高优先级的中断共享总线时。

3.4 效率:你的带宽用了几成?

效率这个指标,说白了就是有效数据占物理传输量的比例。我一般用这个简单公式:

效率 = 有效数据位数 / (物理层传输的总位数) × 100%

还是上面那个 SPI Flash 例子:传 256 字节有效数据,物理层实际发了命令(8位) + 地址(24位) + 空周期(8位) + 256字节数据 + 状态轮询(40位)。总位数约 2208 位。效率 = 2048 / 2208 ≈ 92.8%。

92.8% 看起来不差?但你要知道这是理论最优。实际加上 DMA 请求延迟、CPU 介入、DMA 传输完成中断响应等时间,有效吞吐率往往只有物理层速率的 60%~80%。

我遇到最离谱的一个案例:客户在 SPI 上用了一个极度碎片化的传输策略——每次只发 8 字节就重新配置 DMA。结果物理层 50 MHz,实际有效吞吐率只有 11 Mbps,效率低到 22%。后来改成双缓冲 + 批量传输,直接提到 41 Mbps。

3.5 一张图看懂吞吐率构成

下面这张图把整个数据从应用层到物理线的过程拆开了。你可以看到每一层都会吃掉一部分时间,最终落到“有效吞吐率”那个框里才是你真正在意的东西。

吞吐率构成与开销来源 应用数据 协议封装开销 DMA请求延迟 SPI传输 总线竞争/仲裁 软件栈后处理 有效吞吐率 = 有效数据量 / (所有开销耗时之和) 每个阶段都在吃掉时间,吃掉带宽 协议封装 → 多余的位。DMA请求延迟 → 等待的周期。总线竞争 → 排队的时间。软件后处理 → 不必要的消耗。 优化方向:增大传输粒度、减少请求次数、使用双缓冲隐藏延迟。

说白了,吞吐率就是所有环节一层层扣除后的净值。我每次做性能评估,都会把这张图画一遍,把每个环节的耗时标上去。哪个环节占比最大,就优先优化哪个。通常在 SPI 项目里,DMA 请求延迟和协议封装是最大的两块。如果二者能同时优化,吞吐率提升 30%~50% 并不难。

总结:吞吐率的本质是“有效数据与总时间的比值”。衡量效率时,不要只看物理层速率,更不要忽视那些微秒级的 DMA 延迟。每微秒都宝贵——尤其是在你追求高帧率或高数据吞吐的系统中。