高频调用的小型纯函数能接近机器码效率,关键在于运行时被识别为稳定热点并启用内联缓存——即缓存「调用点→具体实现」的动态绑定结果,跳过每次查表、类型判断与方法解析等开销。

高频调用的小型纯函数能接近机器码效率,关键不在“写得短”,而在运行时被解释器或编译器识别为稳定热点,并启用内联缓存(Inline Cache)机制——它把「调用点 → 具体实现」的动态绑定结果缓存下来,跳过每次查表、类型判断、方法解析等开销。
内联缓存不是编译期 inline,而是运行时的“调用点记忆”
很多人混淆 inline 关键字和内联缓存。前者是 C++/Rust 等静态语言在编译期展开函数体;后者是 Python、JavaScript、Ruby 等动态语言在运行时对「某个特定调用位置」反复调用同一目标函数的行为做缓存。比如:
-
obj.method()第一次执行要查obj的类、找method在 vtable 或 method table 中的位置、确认是否被重写 - 第二次起,如果
obj类型没变、method没被 monkey patch,引擎就直接跳转到上次记下的地址 - 这个缓存条目就挂在调用点(call site)上,不是函数本身上
为什么小型纯函数最容易命中内联缓存?
内联缓存生效的前提是「调用点行为稳定」。小型纯函数天然满足这些条件:
- 输入类型收敛:如
def add(a: int, b: int) -> int:在循环中反复传入int,类型不会突变为float或str - 无副作用:不修改全局状态、不触发 GC、不调用外部 I/O,引擎可安全假设多次调用等价于单次展开
- 调用密度高:在 hot loop 里每毫秒调用成百上千次,V8/CPython 的 JIT 或自适应优化器会快速标记该 call site 为 monomorphic(单态)并填入缓存
- 函数体小:缓存命中后跳转过去执行的代码段短,指令局部性好,CPU 流水线不易 stall
Python 中的内联缓存实际表现(以 CPython 3.12+ 为例)
CPython 从 3.11 引入自适应解释器(adaptive interpreter),并在 3.12 加强了 call site 缓存策略。你不需要手动加 @inline,但可以观察其效果:
- 错误现象:
AttributeError或TypeError在某次调用后突然变慢?说明内联缓存失效,退回到慢路径(megamorphic fallback) - 验证方式:用
dis.dis(func)看不到明显变化,但用perf record -e cycles:u python script.py能测出 call site 的 cycle 数下降 40%+ - 破坏缓存的典型操作:在循环中混用不同类型的对象调用同一方法(如
[1,2].append(x)和"s".join(...)共享同一个 call site) - CPython 不缓存全局函数调用(如
len()),但会缓存 bound method(如lst.append)——所以for x in data: lst.append(x)比for x in data: list.append(lst, x)更快
别指望手动“强制内联”,重点控制调用稳定性
你在 Python 里写 inline 关键字没用,CPython 忽略它;用 @functools.lru_cache 是另一回事,它缓的是返回值,不是调用路径。真正提升内联缓存命中率的做法很朴素:
- 避免在热路径中动态改变对象类型(比如循环里一会儿传
list,一会儿传tuple) - 用局部变量保存 bound method:
append = lst.append; for x in data: append(x) - 不用
getattr(obj, "method")()这类反射调用——它彻底绕过内联缓存 - 纯函数尽量用不可变参数(
int,str,tuple),避免传dict或自定义类实例引发额外属性查找
内联缓存真正的复杂点在于:它依赖运行时统计而非静态分析,同一行代码在不同数据分布下可能走快路或慢路。你以为稳了,其实只是还没触发多态退化。

















