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。知道账在哪,才知道哪里该省。

相关推荐
yuyuxun15 小时前
协同过滤的校园二手物品智能推荐系统的设计与实现 毕业设计-附源码40082
javascript·vue.js·spring boot·python·数据分析·django·课程设计
如夏倾城5 小时前
百度文心(容易被忽略的好用AI)使用方式
人工智能·python·百度·语言模型·dubbo
用户019027581615 小时前
quotes 实时价和 klines 最新收盘价为什么对不上?
python
少司府6 小时前
嵌入式入门篇(二):初识STM32F103ZET6
linux·python·stm32·单片机·嵌入式硬件·mcu·嵌入式软件
打工仔折腾 AI7 小时前
Changedetection.io 网页变动监控:Docker 部署、通知配置与公网暴露实践
运维·人工智能·后端·python·docker·容器·性能优化
火云代理7 小时前
Python 使用代理 IP 前,先弄清 Requests 的配置来源与验证方法
网络·python·网络协议·tcp/ip·ip
2601_967979128 小时前
Python第七十四讲 异常处理完全精通 try-except零基础保姆级教程
python·异常处理·编程教程·错误处理·try-except
wuyk5559 小时前
Spider-第07讲-JSON接口与正则提取
爬虫·python·json
2601_967987379 小时前
Sqlite参考手册
数据库·python·sqlite·api·教程
用户0190275816110 小时前
A 股到价提醒机器人:AlphaFeed + 飞书/钉钉 Webhook
python