Python 的函数调用到底贵在哪?拆开一次 call:51 纳秒栈帧与 *args 的 40 纳秒罚单

写 Python 的人常被教育「函数调用有开销,热点路径要内联」。可这个开销到底多大、由什么构成?本文用 CPython 3.13.14 实测,把一次函数调用拆到底。

一、结论先行

一次空函数调用实测 97.8 ns ------减去空 for 循环体本身的 47.0 ns,调用机制本身约占 51 ns 。这个数字决定了后面所有的取舍:日常业务代码里,一次调用成本可以忽略;百万次级热点循环里,它值得算账。

二、一次调用的字节码:CALL 背后发生了什么

调用方的字节码简单得惊人:

objectivec 复制代码
LOAD_GLOBAL   1 (callee + NULL)
LOAD_CONST    1 (41)
CALL          1
RETURN_VALUE

但 CALL 指令在解释器内部要做一串事:创建栈帧对象、绑定参数到局部变量 、压入调用栈、执行函数体、弹出栈帧。3.11 起帧对象改为惰性 materialize(RETURN_VALUE 后不再物化),调用才便宜下来。

实测空调用 97.8 ns 里,主要就是这套帧生命周期。

三、参数形式:*args 最贵,默认参数反而便宜

同一函数体、不同参数签名,1M 次调用实测:

签名 ns/次 相对 f0()
f0() 141.2 基准
f1(a) 148.5 +5%
f3(a, b, c) 154.2 +9%
fd(a, b=1, c=2) 156.2 +11%
fs(a, *args) 178.9 +27%
fk(a, **kw) 162.7 +15%

规律很直白:*args 要把变长参数打包成 tuple,**kw 要建 dict,都比位置参数贵 。默认参数几乎免费------因为默认值在函数定义时 求值一次存进 __defaults__,调用时直接取引用,不是每次重算。

这也解释了经典陷阱:def f(x=[]) 的可变默认参数之所以危险,正是因为那个 list 只创建了一次,跨调用共享。

四、递归的账:fib(30) 的 200 ms

fib(30) 实测 200 ms,产生约 269 万次调用------摊到每次调用约 74 ns(含 fib 内部的两次加法与比较)。纯调用机制占比可见一斑。

递归深度 2000 层无压力,默认 recursionlimit 是 10 万------限制存在的意义是保护 C 栈,不是 Python 层。

五、绑定方法:c.m() 比 C.m(c) 快 40%

同一个方法两种调法:

scss 复制代码
c.m()    120.4 ns/次
C.m(c)   202.8 ns/次

c.m() 走的是 descriptor 协议预绑定的 method 对象,少一次「把 self 显式塞进参数表」;C.m(c) 是普通函数调用加一次参数绑定。代码里热点路径的 getattr(obj, name)(...) 动态分发,付的就是这条链路。

六、内联的收益上限:48 ns

同一段 1 + 2,函数调用 139.0 ns vs 内联 91.1 ns,差 48 ns。

这就是内联优化的天花板量级。Python 没有 C 意义上的内联展开(3.13 的实验性 JIT 另论),手动内联省的是这 48 ns。百万次循环省 48 ms------值不值,看你的循环在干什么。

七、实战守则

  • 热点循环里消灭属性查找和方法调用 :循环内 self.x、obj.method() 先提取到局部变量,省的是每次的 dict 查找加绑定
  • 拒绝 *args/**kw 进热点签名:固定参数位置,比变长快 12-27%
  • 可观测性用 frame 对象 :inspect.currentframe()、f_back、f_locals 是现成的调试入口,f_back 链条就是调用栈------但别在生产代码里留着
  • 默认参数定义时求值 :def f(ts=time.time()) 拿到的是导入时刻的时间戳,要「每次求值」请用 None 哨兵

一句话总结:Python 的函数调用不贵,但也不免费------51 ns 的帧创建成本,乘以百万次循环就是 50 ms。知道账在哪,才知道哪里该省。

相关推荐
程序猿乐锅2 小时前
【从0-1学习Python | 第七篇】Python 面向对象基础
开发语言·笔记·后端·python·学习·1024程序员节
lzqrzpt3 小时前
太阳能控制器选型要点:电路工艺与工程适配技术解析
python·1024程序员节
databook3 小时前
时间序列数据中,如何找出缺失值
python·数据分析
小小龙学IT4 小时前
Python Polars 高性能数据处理引擎深度解析
开发语言·python·rust·1024程序员节
csdn_aspnet4 小时前
JavaScript 未排序数组中第 k 个最小/最大元素 (预期线性时间)
python
quantdash_cc5 小时前
如何利用标的信息为研究股票池补充名称和交易所信息?
开发语言·python·pandas·量化交易·1024程序员节·股票数据·quantdash
数字化转型分享点滴5 小时前
机加工企业上大数据分析系统有哪些容易踩的坑?
python·1024程序员节
B2_Proxy5 小时前
Scrapy 代理 IP 调度进阶:基于 Redis 与中间件的高可用架构实战
python·scrapy·1024程序员节
在世修行5 小时前
智学文枢:AI 客户端封装
python·http·ai·1024程序员节·pyside