1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
title: "使用卡尔曼滤波器减少服务器请求(或关于过度设计的愉悦)"
date: 2026-03-12T19:00:00+01:00
draft: false
slug: "bayesian-estimator-adaptive-polling-battery"
slug_en: "bayesian-estimator-adaptive-polling-battery"
description: "如何通过一个简单的卡尔曼滤波器减少80%的网络请求,应用于一个菜单栏应用,省电、自动适应电池状态,在你无操作时自动休眠。"
tags: ["卡尔曼", "贝叶斯", "轮询", "电池", "macos", "swift", "工程"]
categories: ["项目"]

translation:
  hash: ""
  last_translated: ""
  notes: |
    - "关于卡尔曼的使用介绍以及通过轮询优化减少请求次数"
---

我有一个菜单栏应用,需要知道一个数字。一个从 0 到 100 的百分比。为了得到这个数字,我必须每30秒访问一次服务器。

我们来算一笔账:30秒一次请求等于1分钟2次请求,1小时120次,一天工作的8小时里就是960次。每天为了获取**一个有时候20分钟都不变的数字**发送将近1000个HTTP请求……

这根本不是监控!简直就是对服务器的骚扰。

## 真正的问题其实不是技术问题,而是政治问题。

当你依赖一个你无法控制的API时——它既不是公开的,也没有明确的速率限制文件,同时属于一家随时可能变更服务条款的公司——每一条不必要的请求都带着风险。这风险不仅是*超时*的风险,还有被切断访问权限的风险。

我用的服务端 *endpoint* 并没有公开的文档支持。它目前是可用的,已经运行了几个月了。但我知道,我发出的每一条请求,都会增加Anthropic某台服务器日志里的条目,假以时日,可能某天他们会决定第三方应用制造的"噪声"已经太多了,直接把我的服务切断。

所以问题的关键不是“怎样更快地轮询”,而是**“怎样尽量少地轮询,同时还不遗漏关键信息?”**

这时候,一个理性的工程师可能写一个简单的`if`,但一个对自嘲式过度设计充满*愉悦感*的工程师,可能最终选择实现一个卡尔曼滤波器。

## 最简单的解决方案(以及它为何失败)

最直接的想法很简单:如果这个数字没变,就别问了。

如果 当前值 == 上一个值: 增加等待时间 否则: 回到30秒轮询


这个方法表现得很差。因为这个数字只有在**你有操作**的时候(比如你发送了消息,或使用了 tokens)才会改变。但在你**没有操作**时,它也会变化——比如系统设置了5个小时的滑动窗口期限,导致过期的tokens自动失效。如果你在其他设备上也在用这项服务,数字也会在你不知情的情况下变动。

所以单靠检查数字是否变化是不够的。你需要的是**预测**它什么时候会发生变化,并以多大的信心预测。

## 卡尔曼滤波器:五分钟速成

卡尔曼滤波器是一种用来融合两种不完美信息的工具。

想象你身处一个没有窗户的房间中特别想知道室外的气温。你有两个渠道:

1. **你的心理模型**:例如 “现在是三月的下午三点,气温应该在18℃左右”。这个估计是合理的,但并不完美——可能外面刚下过雨,或者有风。
2. **一个嘈杂的廉价温度计**:你可以走到阳台测量,但这个温度计有±3℃的波动误差。

两种渠道都不完美,但卡尔曼滤波器告诉你:**把这两种信息结合起来,并在不同时刻对更可靠的信息赋予更大的权重。**

如果你刚刚10秒前查看了温度计,你的心理模型就很可靠——完全可以信任它,不用再去测量。如果你已经一个小时没测量了,你的模型就会失去可信度——这时候你得回阳台看看。

关键是**方差**:一个用来衡量“我对当前估计值有多信任”的数值。在刚看过温度计后的瞬间,方差为零;随着时间推移,方差逐渐增大。当超过某个门槛时,滤波器会告诉你:“不可信了,去获取真实数据吧。”

在我的案例里:

- **心理模型** = tokens的本地使用情况。我知道Claude Code的使用记录,所以大致可以推算出消耗的token数是多少。
- **测量数据** = Anthropic的API真实返回值。它是当前最准确的信息,但获取每一次数据都有政治代价和电力消耗。
- **方差** = 随时间增长的不确定性。如果我在浏览器或手机上使用了服务,本地模型对此一无所知——也因此预测的准确性会下降。

一个复杂的多维卡尔曼滤波器(带协方差矩阵)用来解决这个问题有点类似杀鸡用牛刀。我的方法更简单:一个单一状态(token使用量)、单一传感器(API返回值)、线性模型(成本/预算比例)。20行代码就搞定了,这是可以完美解决问题的最小实现。

翻译到我的具体需求中:

- **预测**: `使用量估计 = 上次真实值 + (本地新增消耗 / 总预算) × 100`
- **修正**: 每次服务器返回一个新的真实值,滤波器会将方差重置为零。
- **不确定性**: 方差开始按照时间线性增长。`σ = √(Q × 自上次校正起的秒数)`。

## 关键点:滤波器自动决定何时请求

这里正是过度设计显得合理的地方。卡尔曼滤波器不仅仅能估计值,它还能**决定何时需要获取真实数据**。五条规则,每个“tick”执行一次:

| 规则 | 触发条件 | 原因 |
|-------|-----------|---------|
| 窗口已重置 | `now ≥ resetsAt` | tokens过期了,之前的值失效。 |
| 高不确定性 | `σ > 5%` | 对预测值信任度低,必须确认。 |
| 范围边界触发 | 置信区间触碰到80%、95%或100% | 临近重要阈值,用户必须知道。 |
| 邻近性触发 | `使用率`接近边界8% | 有可能跨越范围,但本地估计未能察觉(比如在其他设备使用服务时)。 |
| 安全超时 | 15分钟无真实数据返回 | 预防性措施,监控软件的偏执有时是优点。 |

如果没有规则触发,卡尔曼滤波器会说:“没问题,我全都掌握”——于是应用**不会发起HTTP请求**。显示给用户的值是本地估计值。

重点来了:本地估计不需要**网络消耗、功耗或风险**。它完全基于内存中的数学运算。

## 数字对比:前后变化

小范围内token消耗稳定的一天(中度使用,较少峰值):

| 场景 | 请求/小时 | 请求/天(8小时) |
|---------|:-:|:-:|
| 固定30秒轮询 | 120 | 960 |
| 使用贝叶斯估计 | 15-30 | 120-240 |
| 估计 + 休眠 | 4-10 | 30-80 |

相比之前的960次请求,网络请求减少了**75-97%**。对于“只是偶尔请求数据”的任务来说,不算太糟吧?

...(内容会继续翻译完整)