numba - guvectorize 几乎比 jit 快

Posted

技术标签:

【中文标题】numba - guvectorize 几乎比 jit 快【英文标题】:numba - guvectorize barely faster than jit 【发布时间】:2017-06-07 19:47:03 【问题描述】:

我试图并行化在许多独立数据集上运行的蒙特卡洛模拟。我发现 numba 的并行 guvectorize 实现仅比 numba jit 实现快 30-40%。

我在 *** 上发现了这些(1、2)类似的主题,但它们并没有真正回答我的问题。在第一种情况下,由于退回到对象模式而减慢了实现速度,而在第二种情况下,原始发帖人没有正确使用 guvectorize - 这些问题都不适用于我的代码。

为了确保我的代码没有问题,我创建了这段非常简单的代码来比较 jit 和 guvectorize:

import timeit
import numpy as np
from numba import jit, guvectorize

#both functions take an (m x n) array as input, compute the row sum, and return the row sums in a (m x 1) array

@guvectorize(["void(float64[:], float64[:])"], "(n) -> ()", target="parallel", nopython=True)
def row_sum_gu(input, output) :
    output[0] = np.sum(input)

@jit(nopython=True)
def row_sum_jit(input_array, output_array) :
    m, n = input_array.shape
    for i in range(m) :
        output_array[i] = np.sum(input_array[i,:])

rows = int(64) #broadcasting (= supposed parallellization) dimension for guvectorize
columns = int(1e6)
input_array = np.ones((rows, columns))
output_array = np.zeros((rows))
output_array2 = np.zeros((rows))

#the first run includes the compile time
row_sum_jit(input_array, output_array)
row_sum_gu(input_array, output_array2)

#run each function 100 times and record the time
print("jit time:", timeit.timeit("row_sum_jit(input_array, output_array)", "from __main__ import row_sum_jit, input_array, output_array", number=100))
print("guvectorize time:", timeit.timeit("row_sum_gu(input_array, output_array2)", "from __main__ import row_sum_gu, input_array, output_array2", number=100))

这给了我以下输出(时间确实有所不同):

jit time: 12.04114792868495
guvectorize time: 5.415564753115177

同样,并行代码几乎快两倍(仅当行数是 CPU 内核数的整数倍时,否则性能优势会减弱)即使它利用所有 cpu 内核和仅 jit 代码使用一个(使用 htop 验证)。

我在一台配备 4 个 AMD Opteron 6380 CPU(总共 64 个内核)、256 GB RAM 和 Red Hat 4.4.7-1 操作系统的机器上运行它。 我将 Anaconda 4.2.0 与 Python 3.5.2 和 Numba 0.26.0 一起使用。

如何进一步提高并行性能或我做错了什么?

感谢您的回答。

【问题讨论】:

作为参考,我在 2012 Macbook Air 1.6 GHz 上获得了 12 秒和 3.8 秒的时间。因此,尽管您的机器“更好”,但您的 JIT 时间与我的相同,而您的 guvectorize 时间更差。 您可能还想使用一些随机数据检查您的两个函数。它们不会产生相同的结果。 @JoshAdel 我使用以下方法进行了测试:input_array = np.random.rand(rows, columns)np.array_equal(output_array, output_array2) 返回 True @JohnZwinck 我在不同的机器上运行代码,速度较慢,速度较慢,guvectorize 加速在较慢的机器上更大,所以我怀疑下面 MSeifert 的评论是正确的。 @DriesVanLaethem 你是对的。不确定我最初在测试中做了什么,但现在他们同意了。我的道歉 【参考方案1】:

那是因为np.sum 太简单了。使用 sum 处理数组不仅受 CPU 限制,还受“内存访问”时间的限制。因此,向其添加更多内核并不会产生太大影响(当然,这取决于相对于您的 CPU 的内存访问速度)。

仅用于可视化np.sum 是这样的(忽略data 以外的任何参数):

def sum(data):
    sum_ = 0.
    data = data.ravel()
    for i in data.size:
        item = data[i]   # memory access (I/O bound)
        sum_ += item     # addition      (CPU bound)
    return sum

因此,如果大部分时间都花在访问内存上,那么如果将其并行化,您将不会看到任何真正的加速。但是,如果 CPU 密集型任务是瓶颈,那么使用更多内核将显着加快您的代码速度。

例如,如果您包含一些比加法更慢的操作,您会看到更大的改进:

from math import sqrt
from numba import njit, jit, guvectorize
import timeit
import numpy as np

@njit
def square_sum(arr):
    a = 0.
    for i in range(arr.size):
        a = sqrt(a**2 + arr[i]**2)  # sqrt and square are cpu-intensive!
    return a

@guvectorize(["void(float64[:], float64[:])"], "(n) -> ()", target="parallel", nopython=True)
def row_sum_gu(input, output) :
    output[0] = square_sum(input)

@jit(nopython=True)
def row_sum_jit(input_array, output_array) :
    m, n = input_array.shape
    for i in range(m) :
        output_array[i] = square_sum(input_array[i,:])
    return output_array

我在这里使用了IPythons timeit,但它应该是等效的:

rows = int(64)
columns = int(1e6)

input_array = np.random.random((rows, columns))
output_array = np.zeros((rows))

# Warmup an check that they are equal 
np.testing.assert_equal(row_sum_jit(input_array, output_array), row_sum_gu(input_array, output_array2))
%timeit row_sum_jit(input_array, output_array.copy())  # 10 loops, best of 3: 130 ms per loop
%timeit row_sum_gu(input_array, output_array.copy())   # 10 loops, best of 3: 35.7 ms per loop

我只使用了 4 个核心,所以这非常接近可能的加速极限!

请记住,并行计算只能显着加快计算速度如果作业受 CPU 限制

【讨论】:

令我惊讶的是,OP 的“4x AMD Opteron 6380”机器的性能并不比我的 2012 Macbook Air 好,后者的内存子系统肯定更差(对吧?)。 我测试的机器有一块 Supermicro H8QG6-F 主板和 16 x 16GB DDR3-1600 注册 RAM。不知道这个设置是比 Macbook Air 的内存子系统慢还是快。 @MSeifert 感谢您清晰而详尽的解释。我正在重组我的数据集和代码以限制访问内存的次数。

以上是关于numba - guvectorize 几乎比 jit 快的主要内容,如果未能解决你的问题,请参考以下文章

Numba 代码比纯 python 慢

为啥在迭代 NumPy 数组时 Cython 比 Numba 慢得多?

为啥这个 numba 代码比 numpy 代码慢 6 倍?

即使对于巨型矩阵,NUMBA CUDA 也比并行 CPU 慢

分配给数组时Numba慢吗?

一行代码实现Python运行性能增强百倍,性能发动机numba模块介绍