我试图平行化在许多独立数据集上运行的蒙特卡罗模拟.我发现numba的并行guvectorize实现比numba jit实现快了不到30-40%.
我发现这些(1,2#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验证).
我在一台配备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.
如何进一步提高并行性能或我做错了什么?
谢谢您的回答.
那是因为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
我在这里使用了IPythonstimeit
,但它应该是等价的:
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限制,并行计算只能显着加快计算速度.