You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何timeit测量该Numba函数的执行耗时结果不准确?

问题根因

测量结果偏差和Numba编译无关,是测量逻辑的变量没控制好:

  • 你的go_fast函数每次调用会生成一个1000×1000的float64数组,单个数组占8MB内存。你用perf_counter做单次测量时,前一行print(go_fast(x))执行过程中会持有返回数组的引用,直到print逻辑完全结束才会释放,第二次调用(perf_counter包裹的那次)需要向操作系统申请新的内存页,触发缺页中断,额外增加了大量耗时,这就是你测得单次0.0026s的来源。
  • 你写的timeit调用里,setup阶段的print(go_fast(x))会在正式计时开始前执行完毕,返回的数组引用会被立即回收,后续100次计时调用会反复复用已经申请过的内存块,没有缺页中断、新内存映射的额外开销,因此总耗时更低,和你单次测量的场景完全不一致。
  • setup里加print属于多余操作,虽然IO耗时不会计入正式计时,但控制台输出缓冲区刷新的残留状态可能轻微影响前几次调用的性能稳定性。
正确测量方案

测JIT编译函数的性能,必须先做充分预热,控制所有无关变量:

  • 提前调用2-3次目标函数,覆盖JIT编译、首次内存缺页、CPU睿频拉起的预热阶段
  • 预热完成后手动触发一次垃圾回收,避免测量过程中GC随机触发带来的波动
  • 预热阶段不要保留返回值的多余引用,保证测量时内存状态稳定
  • 测量逻辑里不要混入print等IO操作

参考代码:

import numba as nb
import numpy as np
from time import perf_counter
from timeit import timeit
import gc

x = np.arange(1_000_000).reshape(1000, 1000)

@nb.njit(fastmath=True)
def go_fast(a):
    trace = 0.0
    for i in range(a.shape[0]):
        trace += np.tanh(a[i, i])
    return a + trace

# 预热阶段:编译+跑通逻辑+完成首次内存分配
for _ in range(3):
    _ = go_fast(x)
_ = None
gc.collect()

# perf_counter多次测量取平均
test_round = 1000
t0 = perf_counter()
for _ in range(test_round):
    go_fast(x)
t1 = perf_counter()
print(f"perf_counter测得单次平均耗时: {(t1-t0)/test_round:.9f}s")

# 正确的timeit写法
time_cost = timeit(
    stmt="go_fast(x)",
    setup="gc.collect()",
    globals=globals(),
    number=test_round
)
print(f"timeit测得单次平均耗时: {time_cost/test_round:.9f}s")

按这个逻辑测量,两种方法得到的结果差值会在5%以内,属于正常波动范围。

注意:如果要严格测量纯计算逻辑的耗时,可以把数组加法的结果赋值给一个预分配的数组,避免每次调用时的内存分配开销干扰,更准确反映Numba编译后代码的实际计算性能。

内容的提问来源于stack exchange,提问作者Paul Jurczak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 08:30:48