4、性能对比分析:中断延迟的构成

聊到中断和轮询的性能对比,我习惯先问自己一个问题:延迟到底从哪来的?

很多人一上来就比谁快谁慢,其实没搞清楚延迟的构成。你想想看,中断不是瞬间响应的,轮询也不是一直空转。它们的延迟,各有各的“账本”。

我在项目中遇到过好几次这样的情况:明明中断优先级设得很高,但延迟还是超标。查到最后,发现是硬件路径上多了一级缓冲。嗯,这里要注意,延迟的构成,远比你想的复杂

4.1 中断延迟的构成

中断延迟,说白了就是从硬件信号拉高,到CPU执行第一行中断服务函数的时间。我把它拆成两段:

  • 硬件延迟:信号从外设走到CPU核的路程
  • 软件开销:CPU保存现场、查中断向量表、跳转执行的时间

硬件延迟

这部分其实很“物理”。信号经过总线、中断控制器、可能还有多级桥接芯片。每一级都会引入几个时钟周期的延迟。

举个例子,我在一个FPGA+ARM的混合架构里测过:

硬件环节典型延迟(时钟周期)
外设到中断控制器2~5
中断控制器仲裁3~8
总线传输5~15
CPU核响应1~3

加起来,硬件延迟通常在10~30个时钟周期。如果跑在1GHz,也就是10~30纳秒。看起来不大,但别忘了——这只是硬件部分。

软件开销

这才是大头。CPU收到中断信号后,要做的事不少:

  1. 保存当前上下文(压栈寄存器)
  2. 查询中断向量表
  3. 跳转到ISR入口
  4. 执行ISR
  5. 恢复上下文(出栈)

我实测过一个典型的ARM Cortex-M4平台,软件开销大约在50~120个时钟周期。如果ISR本身再复杂点,这个数字还会翻倍。

中断延迟 = 硬件延迟(10~30周期) + 软件开销(50~120周期)

总延迟通常在60~150个时钟周期,换算成时间大约是60~150纳秒(1GHz主频)。

4.2 轮询延迟的构成

轮询的延迟,其实更好理解。它取决于你多久检查一次状态。

我把它拆成:

  • 轮询间隔:两次检查之间的时间
  • 检测开销:每次检查读寄存器、判断状态的时间

轮询间隔

说白了,就是你多久“看一眼”外设。间隔越短,响应越快,但CPU占用率越高。

举个例子:

轮询间隔最大理论延迟CPU占用率(假设检测开销100ns)
1微秒1微秒10%
10微秒10微秒1%
100微秒100微秒0.1%

你看,轮询延迟 = 轮询间隔 + 检测开销。但实际最坏情况是:事件刚好发生在你检查完的那一刻,所以最大延迟约等于一个轮询间隔

检测开销

这部分包括:

  • 读取状态寄存器(通常1~2个总线周期)
  • 位运算判断状态(几个指令周期)
  • 条件跳转(分支预测可能失败)

我测过,一次简单的轮询检测,开销大约在10~30个时钟周期。比中断的软件开销小得多。

轮询延迟 = 轮询间隔(用户设定) + 检测开销(10~30周期)

总延迟主要由轮询间隔决定,检测开销占比很小。

4.3 不同负载下的表现差异

这里我要说点实际经验了。中断和轮询在不同负载下,表现完全不一样。

低负载场景

事件发生频率很低,比如每秒几次。

  • 中断:CPU大部分时间休眠或做其他事,中断来了才响应。延迟稳定,CPU占用率极低。
  • 轮询:CPU需要不断检查,即使没事件也在空转。浪费CPU资源。

我建议:低负载用中断,省电又高效。

高负载场景

事件发生非常频繁,比如每秒百万次。

  • 中断:频繁的上下文切换会吃掉大量CPU时间。我曾经在一个网络驱动里测过,中断频率超过每秒50万次时,CPU有60%的时间都在做上下文切换,真正处理数据的只有40%。
  • 轮询:没有上下文切换,检测开销固定。CPU可以批量处理事件。

说白了,高负载下轮询反而更高效。这就是为什么很多高性能网卡驱动在收包量大的时候会自动切换到轮询模式。

突发负载场景

平时没事件,突然来一大波。

  • 中断:第一个事件触发中断,后续事件可能被缓冲。但如果中断风暴来了,CPU可能被淹没。
  • 轮询:轮询间隔固定,突发事件来了也只能等下一个检查点。延迟会变大。

我遇到过这种情况:一个传感器数据采集系统,平时每秒只有几个事件,但偶尔会在一毫秒内爆发上千个事件。用中断的话,CPU直接被中断风暴打崩。后来我改成中断+轮询混合模式——平时用中断,检测到高频事件时自动切到轮询。效果很好。

注意:中断风暴不是开玩笑的。我曾经在一个项目里,因为外设的中断信号没有做去抖处理,导致CPU每秒收到几百万次中断,系统直接假死。从那以后,我设计中断系统时一定会加一个“最大中断频率限制”。

4.4 核心逻辑图

下面这张图,是我自己总结的延迟构成对比。你可以把它当作一个快速参考:

中断 vs 轮询 延迟构成对比 中断延迟 硬件延迟(10~30周期) 信号传输 + 仲裁 + 总线 软件开销(50~120周期) 上下文保存 + 向量查询 + ISR 总延迟:60~150周期 轮询延迟 轮询间隔(用户设定) 1μs ~ 100ms 可调 检测开销(10~30周期) 读寄存器 + 位运算 + 跳转 总延迟 ≈ 轮询间隔 对比

这张图把两个模式的延迟构成拆得很清楚。中断的延迟主要来自软件开销,轮询的延迟主要来自轮询间隔。你想想看,选哪个模式,其实就是在选你愿意承受哪种延迟

4.5 我的选择建议

说了这么多,最后给点实际建议:

  • 事件频率低(< 1KHz):用中断,省CPU资源
  • 事件频率高(> 100KHz):用轮询,避免上下文切换开销
  • 中间地带:考虑混合模式,或者自适应切换
  • 延迟要求极严格(< 1μs):轮询+短间隔,或者专用硬件

我曾经在一个交易系统里,要求延迟必须控制在500纳秒以内。中断根本做不到,因为软件开销就占了100多纳秒。最后我们用了FPGA直接处理,连CPU都不经过。嗯,有时候,架构选择比优化更重要。


专注资料整理