Administrator
Administrator
发布于 2025-04-09 / 0 阅读

numba库 核心算法加速

numba库 核心算法加速

用于核心算法加速,加速数值计算。比如numpy库等加速都可以

官方文档:Numba documentation — Numba 0+untagged.1510.g1e70d8c.dirty documentation

使用方法

一、直接使用

问题:Numba 会在第一次调用函数时,根据输入参数的类型动态生成优化后的机器码,这一过程需要时间。但是编译后的机器码会缓存到磁盘(.numba_cache 目录),后续调用直接使用缓存,因此速度极快

解决方案:

  • 方案1、预热调用:可以 构造一个符合的参数,在软件启动时先主动调用一次
  • 方案2、AOT编译:将关键函数 提前编译为二进制文件,直接跳过运行时的 JIT 过程
  • 方案3、缓存优化:利用 Numba 的 磁盘缓存机制,确保编译结果持久化。
    from numba import njit
    
    @njit(cache=True)  # 确保缓存生效
    def your_function(...):
        ...
    

二、AOT编译

效率最高,适合生产环境:尤其适用于需要部署的应用程序。

AOT编译代码:

from numba.pycc import CC
import numpy as np

cc = CC('precompiled')
cc.verbose = True  # 开启调试信息


# ------------------------- 函数定义 -------------------------


def triangleInsideSphere(point, radiusSq, v1, v2, v3):
    def distanceSqToSegment(point, v1, v2):
        v2v1 = v2 - v1
        len_sq = v2v1 @ v2v1

        if np.abs(len_sq) < 1e-20:
            delta = point - v1
            return delta @ delta

        pt = point - v1
        t = (pt @ v2v1) / len_sq

        if t < 0.0:
            return pt @ pt
        elif t > 1.0:
            delta = point - v2
            return delta @ delta
        else:
            projection = pt - t * v2v1
            return projection @ projection


    d1 = distanceSqToSegment(point, v1, v2)
    d2 = distanceSqToSegment(point, v2, v3)
    d3 = distanceSqToSegment(point, v1, v3)
    return d1 < radiusSq or d2 < radiusSq or d3 < radiusSq


def pointInsideTriangle(point, v1, v2, v3):
    eps = 1e-10
    A, B, C = v1, v2, v3
    P = point

    vec0 = C - A
    vec1 = B - A  # 修正变量名冲突
    vec2 = P - A

    dot00 = vec0 @ vec0
    dot01 = vec0 @ vec1
    dot02 = vec0 @ vec2
    dot11 = vec1 @ vec1
    dot12 = vec1 @ vec2

    denom = dot00 * dot11 - dot01 ** 2
    if np.abs(denom) < eps:
        return False

    inv_denom = 1.0 / denom
    u = (dot11 * dot02 - dot01 * dot12) * inv_denom
    v_val = (dot00 * dot12 - dot01 * dot02) * inv_denom  # 修正变量名

    return (u >= -eps) & (v_val >= -eps) & (u + v_val <= 1.0 + eps)


# ------------------------- 显式类型绑定 -------------------------
sig_triangle = 'b1(f4[::1], f4, f4[::1], f4[::1], f4[::1])'
sig_point = 'b1(f4[::1], f4[::1], f4[::1], f4[::1])'

cc.export('triangleInsideSphere', sig_triangle)(triangleInsideSphere)
cc.export('pointInsideTriangle', sig_point)(pointInsideTriangle)

if __name__ == '__main__':
    cc.compile()

运行上述代码之后,会得到一个precompiled.pyd文件(名字取决于上面CC函数定义的名字)

调用方式:直接使用下面的代码引入函数即可

from precompiled import triangleInsideSphere, pointInsideTriangle

显式类型绑定中,符号意义:

类型 对应类型 含义
b1 np.bool_ 布尔返回值
i4 np.int32 32位整数
i8 np.int64 64位整数
f4 np.float32 32位单浮点数
f8 np.float64 64位双精度浮点数
u1 np.uint8 8位无符号整数

注意事项:

  1. 显式类型绑定中,类型必须是正确的,尤其是numpy对象,建议显式定义dtype类型
  2. 预编译的功能,正在等待被弃用。

解决 PyCharm 中未解析引用警告的完整方案

PyCharm 对 Numba AOT 生成的 .pyd 文件中的函数无法正确识别,导致静态检查误报。以下是三种解决方案,按推荐度排序:

方案 1:生成存根文件(.pyi)(推荐)

通过生成类型存根文件,明确告知 PyCharm 函数的参数和返回类型。

步骤 1:安装 mypy 工具

pip install mypy

步骤 2:生成存根文件
在项目根目录执行:

stubgen -m precompiled -o .

生成 precompiled.pyi 文件。

步骤 3:编辑存根文件
打开 precompiled.pyi,根据实际函数签名修改内容:

# precompiled.pyi
from numba import float32, boolean

def triangleInsideSphere(
    point: float32[::1], 
    radiusSq: float32, 
    v1: float32[::1], 
    v2: float32[::1], 
    v3: float32[::1]
) -> boolean: ...

def pointInsideTriangle(
    point: float32[::1], 
    v1: float32[::1], 
    v2: float32[::1], 
    v3: float32[::1]
) -> boolean: ...

步骤 4:配置 PyCharm
• 将存根文件放在与 .pyd 同目录或项目根目录。
右键项目根目录Mark Directory asSources Root


方案 2:动态类型注释(临时解决)

在导入语句后添加类型注释,直接告知 PyCharm 函数存在。

# noinspection PyUnresolvedReferences
from precompiled import triangleInsideSphere, pointInsideTriangle
# type: (np.ndarray, float, np.ndarray, np.ndarray, np.ndarray) -> bool
# type: (np.ndarray, np.ndarray, np.ndarray, np.ndarray) -> bool

方案 3:配置 PyCharm 忽略警告

全局关闭未解析引用检查(不推荐,会丢失其他有效警告)。

步骤

  1. FileSettingsEditorInspections
  2. 搜索 Unresolved references → 取消勾选。

验证与注意事项

  1. 存根文件更新:每次修改 AOT 编译的函数后,需重新生成并更新 .pyi
  2. 类型一致性:确保存根中的类型签名与 Numba 导出完全一致(如 float32 vs np.float32)。
  3. 运行时无影响:存根文件仅用于静态检查,不影响实际执行。

最终效果

方案 1 可永久消除警告,提升代码可维护性。
方案 2 适合快速验证,但需每次手动添加。
方案 3 会隐藏所有未解析引用错误,慎用。