Python numba-guvectorize几乎没有jit快
我试图并行化一个在许多独立数据集上运行的蒙特卡罗模拟。我发现numba的并行guvectorize实现仅比numba jit实现快30-40% 我在Stackoverflow上发现了这些(,)类似的主题,但它们并没有真正回答我的问题。在第一种情况下,由于回退到对象模式,实现速度减慢,在第二种情况下,原始海报没有正确使用guvectorize-这些问题都不适用于我的代码 为了确保我的代码没有问题,我创建了一段非常简单的代码来比较jit和guvectorize:Python numba-guvectorize几乎没有jit快,python,performance,numpy,parallel-processing,numba,Python,Performance,Numpy,Parallel Processing,Numba,我试图并行化一个在许多独立数据集上运行的蒙特卡罗模拟。我发现numba的并行guvectorize实现仅比numba jit实现快30-40% 我在Stackoverflow上发现了这些(,)类似的主题,但它们并没有真正回答我的问题。在第一种情况下,由于回退到对象模式,实现速度减慢,在第二种情况下,原始海报没有正确使用guvectorize-这些问题都不适用于我的代码 为了确保我的代码没有问题,我创建了一段非常简单的代码来比较jit和guvectorize: import timeit impo
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))
这给了我以下输出(时间确实有点不同):
因此,即使并行代码使用所有CPU核心,jit代码仅使用一个(使用htop验证),并行代码的速度也仅为CPU核心数的整数倍,否则性能优势会减弱
我在一台拥有4x AMD Opteron 6380 CPU(总共64个内核)、256GB内存和Red Hat 4.4.7-1操作系统的机器上运行此功能。
我将Anaconda4.2.0与Python3.5.2和Numba0.26.0结合使用
如何进一步提高并行性能,或者我做错了什么
谢谢你的回答。那是因为
np.sum
太简单了。使用sum处理数组不仅受到CPU的限制,还受到“内存访问”时间的限制。因此,向它扔更多的内核并没有多大区别(当然,这取决于相对于CPU的内存访问速度)
仅针对可视化np.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
我在这里使用了,但应该是等效的:
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限制,并行计算只能显著加快计算速度。作为参考,我在2012年Macbook Air 1.6 GHz上的计算时间为12秒和3.8秒。因此,尽管你的机器“更好”,但你的JIT时间和我的一样,而且你的矢量化时间更差。你可能还想使用一些随机数据检查你的两个函数。它们不会产生相同的结果。@JoshAdel我使用以下测试:
input\u array=np.random.rand(行、列)
和np.array\u equal(output\u array,output\u array2)
返回True@JohnZwinck我在不同的机器上运行代码,速度越来越慢,速度越慢的机器上guvectorize的加速比越大,所以我怀疑MSeifert下面的评论是正确的。@DriesVanLaethem你是对的。不确定我最初在测试中做了什么,但现在他们同意了。我的道歉OP的“4x AMD Opteron 6380”机器的性能并不比我的2012 Macbook Air好多少,它肯定有一个更差的内存子系统(对吧?)。我测试的机器有一个超级微型H8QG6-F主板,带有16 x 16GB DDR3-1600注册RAM。不知道此设置是比Macbook Air的内存子系统慢还是快。@MSeifert感谢您的清晰详细的解释。我正在重新构造数据集和代码,以限制访问内存的次数。
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
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