@[toc]

EWMA 指数加权移动平均

在这里插入图片描述

EWMA 是什么

EWMA(Exponential Weighted Moving Average,指数加权移动平均)是一种递推式平滑方法:用一个“带记忆”的平滑值表示最近一段时间的趋势,同时让更久远的数据以指数形式衰减,从而降低短期抖动对决策的影响。

典型形式为:
$$
[
S_t = \alpha \cdot x_t + (1-\alpha)\cdot S_{t-1}
]
$$

  • $(x_t)$:当前观测样本
  • $(S_t)$:平滑后的状态值
  • $(\alpha\in(0,1])$:平滑系数(越大越“敏感”,越小越“稳”)

将递推展开后,历史样本的权重会按 $((1-\alpha)^k)$ 逐步衰减,因此称为“指数加权”。


EWMA 的核心原理

只保存一个状态值,持续吸收新样本

EWMA 不需要保存历史窗口,只需要维护一个状态 (S)。每到一个新样本,就把旧状态衰减一部分,再注入一部分新样本权重:

  • 旧状态贡献:$((1-\alpha)\cdot S_{t-1})$
  • 新样本贡献:$(\alpha\cdot x_t)$

这使得计算成本为 O(1),非常适合内核、驱动、实时系统等对开销敏感的环境。

平滑带来的必然代价:滞后

EWMA 的平滑本质是低通滤波,会产生“惯性”:

  • 抖动会被压平(稳定性提升)
  • 变化会被延迟反映(响应速度下降)

因此 EWMA 适合用于“趋势判断”,不适合用于“必须立刻跟随变化的硬性控制”。


EWMA 的作用

1) 抗抖动:避免策略在临界点频繁翻转

将 0/1 或噪声较大的观测信号转为连续平滑值,可避免“瞬时状态”导致的频繁切换(thrashing)。

2) 形成“近期趋势”的可量化指标

EWMA 能表达“近期忙/近期热/近期拥塞”的程度,而不仅是一次性的瞬时判定。

3) 低成本在线计算

无须维护队列、窗口或历史数组,只维护一个状态值即可长期运行。


适用场景

适合使用 EWMA 的典型场景

  • 资源忙碌度/拥塞度估计:队列是否持续忙、丢包/重试是否持续发生
  • 动态阈值与速率控制:根据平滑后的压力信号调节批量大小、并发度、派发策略
  • 性能指标平滑展示:吞吐、延迟、负载等监控指标需要稳定可读
  • 噪声较大但趋势重要的信号:短期尖峰不应触发策略翻转

不适用场景

不建议使用 EWMA 的场景

  • 必须对突变立即响应
    EWMA 存在滞后,突发故障/突发饥饿/硬实时截止期场景更适合“瞬时触发 + 保护机制”的方式。

  • 样本非均匀时间间隔,但公式未做时间归一化
    EWMA 默认每次更新代表同等时间步长;若更新频率变化很大,需要引入时间因子(time-decay),否则平滑语义会漂移。

  • 需要严格边界或精确统计量
    EWMA 是估计器而不是精确统计:无法替代 P99、直方图、滑动窗口计数等需要“真实分布”的场景。

  • 信号本身已经稳定且无噪声
    平滑会引入不必要的延迟,收益很低。


以 Linux 块层 blk_mq_update_dispatch_busy 为例

blk_mq_update_dispatch_busy 派发忙碌度的 EWMA 平滑更新

目标:用平滑值表达硬件队列“近期是否持续忙”

该逻辑维护 hctx->dispatch_busy 作为“近期忙碌度”的平滑指标,用于派发策略判断(例如:忙时倾向保守派发、减少一次性出队数量;不忙时恢复批量派发以追求吞吐)。

核心设计点:

  • 忙时注入样本:当本轮观察到“资源紧张/派发受限”等忙碌迹象时注入样本
  • 不忙时只衰减:当本轮不忙时不注入,只有指数衰减,使忙碌痕迹自然消退
  • 固定点缩放:避免整数运算导致小值过早被舍入为 0

算法对应的 EWMA 形式

示例中权重定义:

  • WEIGHT = 8
  • FACTOR = 4
  • 忙样本注入值 sample = busy ? (1<<FACTOR) : 0

更新公式等价于:
$$
[
ewma_{new}=\frac{(WEIGHT-1)\cdot ewma_{old} + sample}{WEIGHT}
]
$$
展开后可写为:
$$
[
ewma_{new}=\frac{7}{8}\cdot ewma_{old} + \frac{1}{8}\cdot sample
]
$$
因此该实现相当于:

  • $(\alpha = 1/8 = 0.125)$
  • 旧值保留 (7/8),每次更新指数衰减

FACTOR(缩放因子)的意义

在整数域做 EWMA 时,如果样本只有 0/1:

  • 注入项是 (\alpha\cdot 1 = 1/8)
  • 整数除法会把 1/8 截断为 0
  • 平滑值会“上不去”或“很快归零”

通过 sample = 1<<FACTOR 放大样本,使平滑值在整数域保持足够分辨率:

  • 1<<4 = 16
  • 长期持续忙时 dispatch_busy 会逐步收敛到接近 16
  • 轻微忙碌也能留下可衰减的痕迹

低开销优化:避免无意义写回

这段逻辑:

1
2
if (!ewma && !busy)
return;

含义是:

  • 已经为 0 且本轮也不忙 → 不更新、不写回
  • 降低低负载情况下的写热点与无意义触碰

示例代码:blk_mq_update_dispatch_busy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define BLK_MQ_DISPATCH_BUSY_EWMA_WEIGHT  8
#define BLK_MQ_DISPATCH_BUSY_EWMA_FACTOR 4

static void blk_mq_update_dispatch_busy(struct blk_mq_hw_ctx *hctx, bool busy)
{
unsigned int ewma;

ewma = hctx->dispatch_busy;

if (!ewma && !busy)
return;

ewma *= BLK_MQ_DISPATCH_BUSY_EWMA_WEIGHT - 1;
if (busy)
ewma += 1 << BLK_MQ_DISPATCH_BUSY_EWMA_FACTOR;
ewma /= BLK_MQ_DISPATCH_BUSY_EWMA_WEIGHT;

hctx->dispatch_busy = ewma;
}

从该例总结 EWMA 的工程价值

  • 观测信号:本轮是否出现“忙”的迹象(0/1)
  • 状态变量dispatch_busy(连续平滑值)
  • 决策用途:把“偶发忙”与“持续忙”区分开,避免派发策略跟着单次抖动频繁翻转
  • 实现特征:固定点 EWMA、低开销、无需历史窗口、自然衰减

实战建议:选择 EWMA 参数的经验

  • WEIGHT 越大($(\alpha=1/WEIGHT)$ 越小)→ 更平滑、更迟钝、记忆更长
  • WEIGHT 越小 → 更敏感、更易抖动
  • FACTOR 用于保证整数分辨率,不改变“指数衰减”的结构,只改变量纲精度

调整 EWMA 参数以平衡“敏感”和“稳定”

用 $(\alpha)$ 或 WEIGHT 控制记忆长度

EWMA 基本形式:
$$
[
S_t=\alpha\cdot x_t+(1-\alpha)\cdot S_{t-1}
]
$$
工程实现常写成:
$$
[
S_{new}=\frac{(WEIGHT-1)\cdot S_{old}+sample}{WEIGHT}
\quad\Rightarrow\quad
\alpha=\frac{1}{WEIGHT}
]
$$
参数含义:

  • $(\alpha)$ 越大(WEIGHT 越小):更敏感、更快响应、更容易抖动
  • $(\alpha)$ 越小(WEIGHT 越大):更稳定、更慢响应、滞后更明显
用“半衰期”直观选参

历史权重按 $((1-\alpha)^k)$ 衰减。定义“半衰期步数” $(k_{1/2})$:权重衰减到 50% 所需更新次数:
$$
[
(1-\alpha)^{k_{1/2}}=0.5
\Rightarrow
k_{1/2}=\frac{\ln(0.5)}{\ln(1-\alpha)}
]
$$
用法:

  • 目标:希望“约 $(k_{1/2})$ 次更新后,旧影响减半”
  • 选参:按上式反推 $(\alpha)$,再映射到可实现的 WEIGHT

经验替代指标(粗略但好用):

  • 记忆长度近似 $(\approx \frac{1}{\alpha})$ 个更新步
  • WEIGHT 取 4/8/16/32 往往便于整数实现与调参

FACTOR(缩放)保证整数分辨率

当样本是 0/1 或小整数时,直接做整数 EWMA 容易被截断为 0。固定点缩放做法:

  • sample = busy ? (1<<FACTOR) : 0
  • S 始终在“放大后的量纲”上滚动

关键点:

  • FACTOR 只改变数值量纲与分辨率,不改变指数衰减结构
  • 二值样本持续为 1 时,稳态 $(S)$ 会收敛到 1<<FACTOR 附近(同量纲)

选参要点:

  • 分辨率:FACTOR 越大,低负载下可见的“痕迹”越多
  • 溢出:确保中间项 (WEIGHT-1)*S + sample 不超过整数类型上限
  • 阈值:若策略阈值以 S 判断,阈值也应使用同量纲(例如 threshold = 8 对应 FACTOR=4 时的 0.5 程度)

用“更新频率”校准参数(避免语义漂移)

若采样每 $(\Delta t)$ 秒更新一次,WEIGHT 实际对应“多少秒的记忆”。同一组 WEIGHT 在不同更新频率下语义不同。

做法:

  • 先确定目标记忆时间 $(T)$(例如希望保留约 2 秒趋势)
  • 再按更新频率换算步数 $(N=T/\Delta t)$
  • 再选 $(\alpha \approx 1/N)$(或用半衰期公式更精确)

在非均匀采样间隔下引入时间因子

EWMA 默认每次更新代表同等时间步长。若更新间隔不稳定,需要把“衰减”改为按真实时间衰减(time-decay)。

用时间常数 $(\tau)$ 定义连续时间 EWMA

设两次更新间隔为 $(\Delta t)$,时间衰减系数:
$$
[
decay = e^{-\Delta t/\tau}
]
$$
更新公式:
$$
[
S_t = decay\cdot S_{t-1} + (1-decay)\cdot x_t
]
$$
等价于“动态 $(\alpha)$”:
$$
[
\alpha_t = 1 - e^{-\Delta t/\tau}
]
$$
解释:

  • $(\tau)$ 越大:记忆越长、响应越慢
  • $(\Delta t)$ 越大:本次更新注入权重越大(因为旧值已经衰减更多)

可直接落地的伪代码

1
2
3
4
5
6
7
8
9
// S: fixed-point state
// x: current sample (same scale as S)
// now/last: timestamp in the same unit (ticks, ns, ms, etc.)
// tau: time constant in the same unit

dt = now - last;
decay = exp(-dt / tau); // fixed-point or lookup
S = decay * S + (1 - decay) * x; // keep scale consistent
last = now;

实现建议(整数域):

  • exp() 用查表(按 dt 分桶)或近似(小 dt 时 $(1 - dt/\tau))$
  • decayS 都用固定点(例如 Q16.16),避免浮点依赖
  • dt 过大时可做上限裁剪,避免一次更新“完全重置”语义不符合预期

多级分层的三种常见做法

“多级分层”在工程上常指三类结构:按对象层级分层、按时间尺度分层、按滤波级联分层。

1) 按对象层级分层:局部 EWMA → 汇总 EWMA

用于多队列/多通道/多资源对象的趋势判断:

  • Level 1:每个子对象维护一个局部 $(S_i)$
  • Level 2:上层对象维护汇总 $(S_{agg})$,输入来自 $({S_i})$ 的聚合值(均值、最大值、加权均值等)

示例结构(概念):

  • 子队列忙碌度 $(S_i)$:用于局部派发策略
  • 设备级忙碌度$(S_{dev})$:用于全局限速/整体并发调节

注意点:

  • 上层输入建议是“已经平滑过的量”(例如 $(S_i)$ 的均值),减少噪声放大
  • 聚合函数的选择决定偏好:max 更偏向保护最忙分支,mean 更偏向整体吞吐

2) 按时间尺度分层:快慢两条 EWMA 同时维护

用于同时兼顾“突发检测”和“长期基线”:
$$
[
S^{fast}t=\alpha_f x_t+(1-\alpha_f)S^{fast}{t-1}
]
[
S^{slow}t=\alpha_s x_t+(1-\alpha_s)S^{slow}{t-1}
\quad (\alpha_f>\alpha_s)
]
$$
常见用法:

  • 趋势差:$(\Delta = S^{fast}-S^{slow})$ 表示“近期相对升温”
  • 触发条件:S_fast > S_slow + margin 用于进入保护模式;恢复用更严格条件避免抖动

选参建议:

  • $(\alpha_f)$ 对应“反应速度”(短记忆)
  • $(\alpha_s)$ 对应“基线”(长记忆)
  • margin 与量纲一致(固定点缩放后同样需要同步缩放)

3) 按滤波级联分层:EWMA 输出再做 EWMA

用于更强平滑、接受更大滞后:

  • Stage 1:对原始噪声信号做一次 EWMA
  • Stage 2:对 Stage 1 的输出再做一次 EWMA

效果:

  • 抖动进一步降低
  • 相位滞后增加(变化更慢反映)

适用场景:

  • 展示型指标(监控曲线更“顺”)
  • 非硬实时的策略(更强调稳定性)

不适用场景:

  • 需要快速跟踪变化的控制环(滞后叠加风险更高)

快速落地检查清单

  • 目标响应时间已确定(秒/毫秒/更新步数)
  • 更新间隔是否稳定:不稳定则采用 time-decay 版本
  • 样本范围是否过小:二值/小整数则启用固定点缩放 FACTOR
  • 是否需要兼顾突发与基线:采用快慢双 EWMA
  • 是否存在多对象:采用“对象层级分层”,并明确聚合函数(mean/max/weighted)