numba - guvectorize比jit快得多

时间:2017-01-23 10:23:34

标签: python performance numpy parallel-processing numba

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

我在Stackoverflow上发现了这些(12)可比主题,但它们并没有真正回答我的问题。在第一种情况下,实现通过回退到对象模式而减慢,而在第二种情况下,原始海报没有正确使用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验证)。

我在一台配备4x AMD Opteron 6380 CPU(总共64个核心),256 GB RAM和Red Hat 4.4.7-1 OS的机器上运行。 我使用Anaconda 4.2.0和Python 3.5.2以及Numba 0.26.0。

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

感谢您的回答。

1 个答案:

答案 0 :(得分:17)

那是因为np.sum太简单了。使用sum处理数组不仅受CPU限制,还受“内存访问”时间的限制。因此,抛出更多内核并不会造成差异 (当然这取决于与CPU相关的内存访问速度)。

仅针对vizualisation 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限制,并行计算只能显着加快计算