为何Numba JIT编译的Python代码性能反而低于原生代码?
1. 没处理Numba的编译预热开销
Numba第一次编译JIT函数时,需要把Python代码转成机器码,这个过程本身要花时间。如果你的基准测试直接计时,没先调用一次JIT函数完成编译(也就是预热),编译时间会被算进总耗时,导致结果看起来比原生代码慢很多。
举个反例:
from numba import jit import time @jit(nopython=True) def numba_square_loop(arr): res = [] for x in arr: res.append(x**2) return res # 直接计时,包含编译时间 start = time.time() numba_square_loop([1,2,3]*1000) print(time.time() - start)
正确做法是先预热再计时:
# 先调用一次完成编译 numba_square_loop([1,2,3]) # 再正式计时 start = time.time() numba_square_loop([1,2,3]*1000) print(time.time() - start)
2. 测试用例规模太小
Numba的优势体现在计算密集、循环次数多的场景。如果你的测试数组只有几百个元素,原生Python的循环开销和JIT的编译+执行开销比起来,差距不明显甚至JIT更慢。只有当循环次数达到百万级以上,Numba编译后的机器码优势才能凸显。
3. 用了Numba不擅长的Python容器
Numba的nopython模式(性能最优模式)对普通Python列表的支持有限。比如在JIT函数里用list.append(),因为Python列表是动态扩容的,涉及内存分配等Python runtime操作,这些操作没法被Numba完全优化,反而不如原生Python对列表的成熟优化。
要让Numba发挥优势,应该用它支持的高效结构,比如numba.typed.List,或者直接用Numpy数组(这也是你Numpy相关实现性能正常的原因)。示例:
from numba import jit, typed @jit(nopython=True) def numba_square_typed_list(arr): res = typed.List() for x in arr: res.append(x**2) return res
4. 列表推导、map本身已是C级优化
Python的列表推导和map函数是CPython内部用C实现的,性能本来就很高。当操作本身很简单(比如平方计算)时,原生C实现的开销已经极低,Numba的优化空间很小,自然很难超越。
5. 没启用正确的Numba模式
如果没指定nopython=True,Numba默认用object模式,很多操作还是会调用Python runtime,性能提升有限甚至不如原生。必须确保JIT函数启用nopython=True模式(如果无法进入该模式,Numba会抛出警告,需要检查代码里是否有不支持的特性)。
内容的提问来源于stack exchange,提问作者IgorAlves

