为何接收Python列表的简单Numba函数运行如此缓慢?
运行以下代码耗时2秒,且耗时随列表大小线性增长:
import numba from typing import List @numba.njit def test(a: List[int]) -> int: return 1 test([i for i in range(2_000_000)])
使用numba.typed.List包装输入参数耗时更长(所有时间都消耗在numba.typed.List调用上)。多次调用该函数(仅定义一次)耗时也未改善,说明问题并非出在编译时间上。
实际场景中,原始数据来自外部库,该库无法直接返回numpy数组或numba列表,只能返回Python列表。当前环境:Numba 0.59.1、Python 3.12、4核Ubuntu22笔记本、16GB内存。
请问是否有办法让Numba直接使用原生Python列表?
为什么原生Python列表在Numba中运行慢?
Numba的静态编译模型依赖同质、内存连续的类型,而原生Python列表是动态异质结构(即使声明为List[int],Python仍允许列表内混入其他类型)。因此每次调用@njit装饰的函数时,Numba必须遍历整个列表,检查每个元素类型并拷贝到内部同质数组中——这个过程开销随列表大小线性增长,且无法在多次调用间缓存,这就是你观察到的耗时问题。numba.typed.List的额外耗时则来自其需要先将原生列表转换为Numba类型化列表结构,同样是一次性但更重的转换开销。
可行的优化方案
1. 一次性转换为Numpy数组(推荐)
虽然外部库返回Python列表,但可以在第一次获取数据后一次性转换为numpy数组,后续调用函数时传入数组。Numpy数组是同质内存连续结构,Numba可以直接操作其内存,无需每次转换,彻底消除线性耗时:
import numba import numpy as np from typing import List @numba.njit def test(a: np.ndarray) -> int: return 1 # 从外部库获取原生Python列表 py_list = [i for i in range(2_000_000)] # 仅需一次转换 np_arr = np.array(py_list, dtype=np.int64) # 后续调用速度极快 test(np_arr)
转换开销是一次性的,适合需要多次调用函数的场景。
2. 使用objmode绕过列表类型处理(仅适用于无需操作列表元素的场景)
如果你的函数根本不需要访问列表元素(比如示例中仅返回固定值),可以在函数内部用numba.objmode让Numba跳过对列表的类型检查和转换,直接执行逻辑:
import numba from typing import List @numba.njit def test(a: List[int]) -> int: # 用objmode包裹无需操作列表的逻辑 with numba.objmode(): pass return 1 test([i for i in range(2_000_000)])
注意:如果需要在函数内操作列表元素,objmode中访问Python对象仍会有原生Python的性能开销,因此这个方法仅适用于无列表元素操作的场景。
3. 升级Numba版本
你使用的Numba 0.59.1是较旧版本,且Python 3.12在该版本中的支持可能不完善。升级到Numba 0.60+(需确认版本与Python 3.12的兼容性),新版本对原生Python列表的转换效率有优化,可能会降低部分耗时。
关键结论
Numba无法直接原生使用Python列表——两者的类型模型本质冲突。所有优化的核心都是避免每次调用时的列表元素转换开销,其中一次性转换为Numpy数组是普适性最强的解决方案。
内容的提问来源于stack exchange,提问作者Bananach

