对于使用 Numba 不支持的许多功能的功能,是否有 Numba 的替代方案?

Is there an alternative to Numba for functions that use many features not supported by Numba?

我知道 Numba 不支持所有 Python 功能,也不支持所有 NumPy 功能。 但是我真的需要加快以下函数的执行时间,它在 scikit-image 库中可用 block_reduce (我没有下载整个包,我刚刚从中提取了 block_reduceview_as_blocks

这是原始代码(我刚刚从文档字符串中删除了示例)。

block_reduce.py

import numpy as np
from numpy.lib.stride_tricks import as_strided


def block_reduce(image, block_size, func=np.sum, cval=0):
    """
    Taken from scikit-image to avoid installation (it's very big)

    Down-sample image by applying function to local blocks.

    Parameters
    ----------
    image : ndarray
        N-dimensional input image.
    block_size : array_like
        Array containing down-sampling integer factor along each axis.
    func : callable
        Function object which is used to calculate the return value for each
        local block. This function must implement an ``axis`` parameter such
        as ``numpy.sum`` or ``numpy.min``.
    cval : float
        Constant padding value if image is not perfectly divisible by the
        block size.

    Returns
    -------
    image : ndarray
        Down-sampled image with same number of dimensions as input image.
    """

    if len(block_size) != image.ndim:
        raise ValueError("`block_size` must have the same length "
                         "as `image.shape`.")

    pad_width = []
    for i in range(len(block_size)):
        if block_size[i] < 1:
            raise ValueError("Down-sampling factors must be >= 1. Use "
                             "`skimage.transform.resize` to up-sample an "
                             "image.")
        if image.shape[i] % block_size[i] != 0:
            after_width = block_size[i] - (image.shape[i] % block_size[i])
        else:
            after_width = 0
        pad_width.append((0, after_width))

    image = np.pad(image, pad_width=pad_width, mode='constant',
                   constant_values=cval)

    blocked = view_as_blocks(image, block_size)

    return func(blocked, axis=tuple(range(image.ndim, blocked.ndim)))


def view_as_blocks(arr_in, block_shape):
    """Block view of the input n-dimensional array (using re-striding).

    Blocks are non-overlapping views of the input array.

    Parameters
    ----------
    arr_in : ndarray
        N-d input array.
    block_shape : tuple
        The shape of the block. Each dimension must divide evenly into the
        corresponding dimensions of `arr_in`.

    Returns
    -------
    arr_out : ndarray
        Block view of the input array.
    """
    if not isinstance(block_shape, tuple):
        raise TypeError('block needs to be a tuple')

    block_shape = np.array(block_shape)
    if (block_shape <= 0).any():
        raise ValueError("'block_shape' elements must be strictly positive")

    if block_shape.size != arr_in.ndim:
        raise ValueError("'block_shape' must have the same length "
                         "as 'arr_in.shape'")

    arr_shape = np.array(arr_in.shape)
    if (arr_shape % block_shape).sum() != 0:
        raise ValueError("'block_shape' is not compatible with 'arr_in'")

    # -- restride the array to build the block view
    new_shape = tuple(arr_shape // block_shape) + tuple(block_shape)
    new_strides = tuple(arr_in.strides * block_shape) + arr_in.strides

    arr_out = as_strided(arr_in, shape=new_shape, strides=new_strides)

    return arr_out

test_block_reduce.py

import numpy as np
import time
from block_reduce import block_reduce

image = np.arange(3*3*1000).reshape(3, 3, 1000)

# DO NOT REPORT THIS... COMPILATION TIME IS INCLUDED IN THE EXECUTION TIME!
start = time.time()
block_reduce(image, block_size=(3, 3, 1), func=np.mean)
end = time.time()
print("Elapsed (with compilation) = %s" % (end - start))

# NOW THE FUNCTION IS COMPILED, RE-TIME IT EXECUTING FROM CACHE
start = time.time()
block_reduce(image, block_size=(3, 3, 1), func=np.mean)
end = time.time()
print("Elapsed (after compilation) = %s" % (end - start))

我遇到了这段代码的许多问题。

例如 Numba 不支持 function 类型参数。但即使我尝试通过为此参数使用字符串来解决此问题(例如 func 将是字符串 "sum" 而不是 np.sum ) 我将陷入更多与 Numba 不支持的功能相关的问题(例如 np.padisinstancetuple 函数等)。

经历每一个问题都非常痛苦。例如,我尝试将 all the code for np.pad from numpy 合并到 block_reduce.py 并将 numba.jit 装饰器添加到 np.pad 但我遇到了其他问题。

尽管有所有这些不受支持的功能,但如果有一种使用 Numba 的聪明方法,我会很高兴。

否则有什么替代 Numba 的方法吗?我知道有一个我从未使用过的 PyPy。如果 PyPy 是我的问题的解决方案,我必须强调我只需要这个单一脚本 block_reduce.py 到 运行 与 PyPy。项目的其余部分应该是 运行 和 CPython.

我还想创建一个 C 模块扩展,但我从未做过。但如果值得尝试,我会去做。

您是否尝试过 运行 详细分析您的代码?如果您对程序的性能不满意,我认为使用 cProfile or py-spy 之类的工具会很有帮助。这可以识别程序中的瓶颈,以及哪些部分特别需要加速。

也就是说,正如@CJR 所说,如果您的程序在 NumPy 中花费大量计算时间,则可能没有理由担心使用即时编译器或类似修改来加速它到您的设置。正如更详细的解释 here,NumPy 速度很快,因为它使用编译语言执行计算密集型任务,因此它可以让您免于担心,并将其抽象化。

根据您的具体计划,可能您的效率可以提高parallelism,但这不是我担心的事情还.

以更笼统的说明结束:虽然优化代码效率当然非常重要,但必须谨慎和慎重地进行。正如 Donald Knuth 所说的那样 "premature optimization is the root of all evil (or at least most of it) in programming". See this stack exchange thread 对此有更多讨论。