常见问题
安装
Numba 无法导入
如果在导入 Numba 时看到异常,并且错误信息以以下内容开头:
ImportError: Numba could not be imported.
以下是一些常见问题及其解决方法。
您的安装在给定环境中包含多个 Numba 版本。
常见的情况包括:
使用 conda 安装 Numba 后,再使用 pip 重新安装。
使用 pip 安装 Numba,然后使用 pip 更新到新版本(pip 重新安装似乎并不总是清理得很干净)。
要解决这个问题,最好的方法是创建一个全新的环境,并使用你选择的包管理器在该环境中安装单一版本的 Numba。
您的安装包中包含适用于Python版本X的Numba,但您当前运行的是Python版本Y。
这通常是由于多种Python环境混淆/不匹配问题引起的。最常见的不匹配问题来自于通过使用不同版本的基础或系统Python安装,将Numba安装到某个Python版本的site-packages/环境中,这通常是通过使用“错误”的``pip``二进制文件发生的。这显然会导致问题,因为Numba依赖的C扩展是绑定到特定Python版本的。要检查这是否可能是问题,可以查看以下路径中的``python``二进制文件:
python -c 'import sys; print(sys.executable)'
匹配您的安装工具的路径和/或匹配报告的安装位置,并且如果所有这些路径中的Python版本匹配。请注意,Python版本
X.Y.A
与X.Y.B
兼容。要解决这个问题,最好的方法是创建一个全新的环境,并确保用于安装Numba的安装工具来自该环境/安装和运行时的Python版本匹配。
您的核心系统库太旧了。
这种情况比较少见,但有时会遇到使用非常旧的(通常是已停止支持的)Linux版本,该版本没有足够新版本的
glibc
库,无法为Numba的共享库解析符号。解决这个问题的方法是更新你的操作系统库/更新你的操作系统。您正在使用一个集成开发环境(IDE),例如 Spyder。
在通过IDE安装Numba时存在一些未知问题,但这些问题似乎是1.或2.的变体,建议采用相同的修复方法。此外,尝试在IDE外部使用命令行进行安装。
如果你遇到的问题不属于上述问题之一,请在 numba.discourse.group 上提问,并尽可能包括 Numba 的安装路径以及以下命令的输出:
python -c 'import sys; print(sys.executable)'
编程
我可以将一个函数作为参数传递给一个 jitted 函数吗?
截至 Numba 0.39,只要函数参数也已被 JIT 编译,你就可以这样做:
@jit(nopython=True)
def f(g, x):
return g(x) + g(-x)
result = f(jitted_g_function, 1)
然而,使用函数作为参数进行调度会有额外的开销。如果这对你的应用程序很重要,你也可以使用工厂函数来在闭包中捕获函数参数:
def make_f(g):
# Note: a new f() is created each time make_f() is called!
@jit(nopython=True)
def f(x):
return g(x) + g(-x)
return f
f = make_f(jitted_g_function)
result = f(1)
提高Numba中函数的调度性能是一项持续的任务。
当我修改全局变量时,Numba 似乎并不在意。
Numba 将全局变量视为编译时常量。如果你想让你的即时编译函数在你修改了全局变量的值后更新自身,一种解决方案是使用 recompile()
方法重新编译它。不过,这是一个相对较慢的操作,因此你可能会选择重新架构你的代码,并将全局变量转换为函数参数。
我可以调试一个jit编译的函数吗?
从 Numba 编译的代码中调用 pdb
或其他类似的高级设施目前不受支持。但是,您可以通过设置 NUMBA_DISABLE_JIT
环境变量来暂时禁用编译。
如何创建一个Fortran顺序的数组?
Numba 目前不支持大多数 Numpy 函数(如 numpy.empty()
)中的 order
参数(由于 类型推断 算法的限制)。您可以通过创建一个 C 顺序的数组然后对其进行转置来解决这个问题。例如:
a = np.empty((3, 5), order='F')
b = np.zeros(some_shape, order='F')
可以重写为:
a = np.empty((5, 3)).T
b = np.zeros(some_shape[::-1]).T
如何增加整数宽度?
默认情况下,Numba 通常会使用机器整数宽度来处理整数变量。在32位机器上,有时你可能需要64位整数的范围。你可以简单地将相关变量初始化为 np.int64``(例如 ``np.int64(0)
而不是 0
)。它将传播到所有涉及这些变量的计算中。
如何判断 parallel=True
是否生效?
如果 parallel=True
转换对一个这样装饰的函数失败了,将会显示一个警告。关于并行诊断的信息,请参见 诊断。
性能
Numba 会内联函数吗?
Numba 提供了足够的信息给 LLVM,使得足够短的函数可以被内联。这仅在 nopython 模式 下有效。
Numba 是否向量化数组计算(SIMD)?
Numba 本身并不实现这些优化,但它允许 LLVM 应用它们。
为什么我的循环没有向量化?
Numba 默认启用 LLVM 中的循环向量化优化。虽然这是一个强大的优化,但并非所有循环都适用。有时,由于内存访问模式等细微细节,循环向量化可能会失败。要查看 LLVM 的额外诊断信息,请添加以下行:
import llvmlite.binding as llvm
llvm.set_option('', '--debug-only=loop-vectorize')
这告诉 LLVM 将 循环向量化 过程的调试信息打印到 stderr。每个函数条目如下所示:
备注
使用 --debug-only
需要 LLVM 在启用断言的情况下构建才能工作。使用 Numba 频道 中的 llvmlite 构建,该构建与启用了断言的 LLVM 链接。
LV: Checking a loop in "<low-level symbol name>" from <function name>
LV: Loop hints: force=? width=0 unroll=0
...
LV: Vectorization is possible but not beneficial.
LV: Interleaving is not beneficial.
每个函数条目之间用空行分隔。拒绝向量化的原因通常在条目的末尾。在上面的例子中,LLVM 拒绝了向量化,因为这样做不会加速循环。在这种情况下,可能是由于内存访问模式。例如,被循环遍历的数组可能不是连续布局的。
当内存访问模式是非平凡的,以至于无法确定访问的内存区域时,LLVM 可能会拒绝并显示以下消息:
LV: Can't vectorize due to memory conflicts
另一个常见的原因是:
LV: Not vectorizing: loop did not meet vectorization requirements.
在这种情况下,矢量化被拒绝,因为矢量化代码的行为可能不同。这是一个尝试开启 fastmath=True
以允许快速数学指令的案例。
为什么从解释器中使用 typed
容器时会更慢?
Numba 的 typed
容器,例如 numba.typed.List
,在 numba.typed
中找到,它们以一种高效的形式存储数据,以便从 JIT 编译的代码中访问。当这些容器从 CPython 解释器中使用时,涉及的数据必须从/转换为容器格式。这个过程相对昂贵,因此会影响性能。在 JIT 编译的代码中不存在这样的惩罚,因此对容器的操作要快得多,通常比纯 Python 的等效操作更快。
Numba 会自动并行化代码吗?
在某些情况下,它可以:
使用
target="parallel"
选项的 Ufuncs 和 gufuncs 将在多个线程上运行。parallel=True
选项用于@jit
将尝试优化数组操作并在并行中运行它们。它还增加了对prange()
的支持,以显式并行化一个循环。
你也可以手动在多个线程上运行计算,并使用 nogil=True
选项(参见 释放 GIL)。Numba 还可以使用其 CUDA 和 HSA 后端针对 GPU 架构进行并行执行。
Numba 能加速短运行时间的函数吗?
不显著。新用户有时期望即时编译此类函数:
def f(x, y):
return x + y
并且比Python解释器获得显著的加速。但Numba在这里能改进的地方不多:大部分时间可能花在CPython的函数调用机制上,而不是函数本身。根据经验,如果一个函数执行时间少于10微秒:就让它保持原样。
例外情况是,如果该函数是从另一个即时编译的函数调用的,那么你应该对其进行即时编译。
在JIT编译复杂函数时存在延迟,我该如何改进?
尝试将 cache=True
传递给 @jit
装饰器。它会将编译后的版本保存在磁盘上以供以后使用。
一个更激进的替代方案是 提前编译。
GPU 编程
如何解决 CUDA 在分叉前初始化
错误?
在Linux上,Python标准库中的 multiprocessing
模块默认使用 fork
方法来创建新进程。由于进程分叉会在父进程和子进程之间复制状态,如果在分叉 之前 初始化了CUDA运行时,CUDA在子进程中将无法正常工作。Numba检测到这一点并会引发一个 CudaDriverError
,消息为 CUDA initialized before forking
。
避免此错误的一种方法是使所有对 numba.cuda
函数的调用都在子进程内进行,或在进程池创建之后进行。然而,这并不总是可能的,因为你可能希望在启动进程池之前查询可用GPU的数量。在Python 3中,你可以更改进程启动方法,如 multiprocessing 文档 中所述。从 fork
切换到 spawn
或 forkserver
将避免CUDA初始化问题,尽管子进程不会继承其父进程的任何全局变量。
与其他工具的集成
我可以“冻结”一个使用 Numba 的应用程序吗?
如果你使用 PyInstaller 或类似的工具来冻结应用程序,你可能会遇到 llvmlite 的问题。llvmlite 需要一个非 Python 的 DLL 来工作,但冻结工具不会自动检测到它。你必须告知冻结工具 DLL 的位置:它通常会被命名为 llvmlite/binding/libllvmlite.so
或 llvmlite/binding/llvmlite.dll
,取决于你的系统。
在Spyder下运行脚本两次时出现错误
在Spyder控制台下运行脚本时,Spyder首先尝试重新加载现有模块。这对Numba效果不佳,并可能产生诸如 TypeError: No matching definition for argument type(s)
的错误。
在 Spyder 偏好设置中有一个修复。打开“偏好设置”窗口,选择“控制台”,然后是“高级设置”,点击“设置 UMR 排除模块”按钮,并在弹出的文本框中添加 numba
。
要使设置生效,请务必重启 IPython 控制台或内核。
为什么Numba会抱怨当前的区域设置?
如果你收到如下错误信息:
RuntimeError: Failed at nopython (nopython mode backend)
LLVM will produce incorrect floating-point code in the current locale
这意味着你遇到了一个 LLVM 错误,该错误导致浮点常量的处理不正确。已知在使用某些第三方库(如 matplotlib 的 Qt 后端)时会发生这种情况。
要解决这个错误,你需要将区域设置强制恢复为其默认值,例如:
import locale
locale.setlocale(locale.LC_NUMERIC, 'C')
我如何获取 Numba 开发版本?
Numba 的预发布版本可以通过 conda 安装:
$ conda install -c numba/label/dev numba
杂项
项目名称“Numba”从何而来?
“Numba” 是 “NumPy” 和 “Mamba” 的结合。Mambas 是世界上最快的蛇之一,而 Numba 使你的 Python 代码快速。
我如何在其他作品中引用/引用/致谢 Numba?
对于学术用途,最佳选择是引用我们的ACM会议论文:Numba: 一个基于LLVM的Python JIT编译器。 你也可以在github上找到`源代码 <https://github.com/numba/Numba-SC15-Paper>`_,包括`预印本pdf <https://github.com/numba/Numba-SC15-Paper/raw/master/numba_sc15.pdf>`_,以防你无法访问ACM网站但仍希望阅读该论文。
如何为一个Numba问题编写一个最小的工作重现器?
一个用于 Numba 的最小工作复现应包括:
重现问题的函数源代码。
一些示例数据以及使用该数据调用重现代码的演示。由于Numba基于类型信息进行编译,除非你的问题是数值型的,否则只需提供正确类型的虚拟数据即可,例如使用
numpy.ones
并指定正确的dtype
/大小/形状来创建数组。理想情况下,将1.和2.放入一个包含所有正确导入的脚本中。在提交之前,确保您的脚本实际执行并重现问题!目标是使脚本可以直接从 issue tracker 复制并由其他人运行,以便他们可以看到您遇到的问题。
制作了一个复现器后,现在移除代码中所有不直接有助于复现问题的部分,以创建一个“最小”复现器。这意味着移除未使用的导入,移除未使用或无影响的变量,移除无影响的代码行,简化表达式的复杂性,并将输入数据缩减到触发问题所需的最小量。
完成上述操作确实有助于 Numba 问题分类流程,并将使您的问题得到更快的响应!
建议进一步阅读 关于编写最小可复现工作示例的内容。