我有一个统一分布的数据系列。我希望利用分发来并行排序数据。对于N个CPU,我基本上定义了N个桶并且并行地对桶进行排序。我的问题是,我没有加快速度。
有什么问题?
from multiprocessing import Process, Queue
from numpy import array, linspace, arange, where, cumsum, zeros
from numpy.random import rand
from time import time
def my_sort(x,y):
y.put(x.get().argsort())
def my_par_sort(X,np):
p_list=[]
Xq = Queue()
Yq = Queue()
bmin = linspace(X.min(),X.max(),np+1) #bucket lower bounds
bmax = array(bmin); bmax[-1] = X.max()+1 #bucket upper bounds
B = []
Bsz = [0]
for i in range(np):
b = array([bmin[i] <= X, X < bmax[i+1]]).all(0)
B.append(where(b)[0])
Bsz.append(len(B[-1]))
Xq.put(X[b])
p = Process(target=my_sort, args=(Xq,Yq))
p.start()
p_list.append(p)
Bsz = cumsum(Bsz).tolist()
Y = zeros(len(X))
for i in range(np):
Y[arange(Bsz[i],Bsz[i+1])] = B[i][Yq.get()]
p_list[i].join()
return Y
if __name__ == '__main__':
num_el = 1e7
mydata = rand(num_el)
np = 4 #multiprocessing.cpu_count()
starttime = time()
I = my_par_sort(mydata,np)
print "Sorting %0.0e keys took %0.1fs using %0.0f processes" % (len(mydata),time()-starttime,np)
starttime = time()
I2 = mydata.argsort()
print "in serial it takes %0.1fs" % (time()-starttime)
print (I==I2).all()
答案 0 :(得分:2)
看起来你的问题是你将原始数组分成几块时所添加的开销量。我接受了您的代码,并删除了multiprocessing
的所有用法:
def my_sort(x,y):
pass
#y.put(x.get().argsort())
def my_par_sort(X,np, starttime):
p_list=[]
Xq = Queue()
Yq = Queue()
bmin = linspace(X.min(),X.max(),np+1) #bucket lower bounds
bmax = array(bmin); bmax[-1] = X.max()+1 #bucket upper bounds
B = []
Bsz = [0]
for i in range(np):
b = array([bmin[i] <= X, X < bmax[i+1]]).all(0)
B.append(where(b)[0])
Bsz.append(len(B[-1]))
Xq.put(X[b])
p = Process(target=my_sort, args=(Xq,Yq, i))
p.start()
p_list.append(p)
return
if __name__ == '__main__':
num_el = 1e7
mydata = rand(num_el)
np = 4 #multiprocessing.cpu_count()
starttime = time()
I = my_par_sort(mydata,np, starttime)
print "Sorting %0.0e keys took %0.1fs using %0.0f processes" % (len(mydata),time()-starttime,np)
starttime = time()
I2 = mydata.argsort()
print "in serial it takes %0.1fs" % (time()-starttime)
#print (I==I2).all()
绝对没有排序,multiprocessing
代码与序列代码一样长:
Sorting 1e+07 keys took 2.2s using 4 processes
in serial it takes 2.2s
您可能认为启动进程并在它们之间传递值的开销是导致开销的原因,但是如果我删除multiprocessing
的所有用法,包括Xq.put(X[b])
调用,它最终会只是稍快一点:
Sorting 1e+07 keys took 1.9s using 4 processes
in serial it takes 2.2s
所以你似乎需要研究一种更有效的方法来将你的数组分解成碎片。
答案 1 :(得分:0)
在我看来,有两个主要问题。
多个进程的开销以及它们之间的通信
产生一些Python解释器会产生一些开销,但主要是将数据传入和传出&#34; worker&#34;进程正在扼杀性能。您通过Queue
传递的数据需要&#34;腌制&#34;和&#34; unpickled&#34;,这对于较大的数据来说有点慢(你需要这样做两次)。
如果您使用线程而不是进程,则不需要使用Queue
。在CPython中使用线程来处理CPU繁重的任务通常被认为效率低下,因为通常你会遇到Global Interpreter Lock,但并非总是如此!幸运的是,Numpy的排序功能似乎正在发布GIL,所以使用线程是一个可行的选择!
数据集的分区和连接
对数据进行分区和加入是这种&#34; bucketsort方法的必然成本,但通过更有效地做到这一点可以有所缓解。特别是这两行代码
b = array([bmin[i] <= X, X < bmax[i+1]]).all(0)
Y[arange(Bsz[i],Bsz[i+1])] = ...
可以重写为
b = (bmin[i] <= X) & (X < bmax[i+1])
Y[Bsz[i] : Bsz[i+1]] = ...
进一步改进我还发现np.take
要快于#34;花哨索引&#34;和np.partition
也很有用。
总结一下,我能做到的最快的是以下内容(但它仍然没有像你想要的那样与内核数量成线性比例)。
from threading import Thread
def par_argsort(X, nproc):
N = len(X)
k = range(0, N+1, N//nproc)
I = X.argpartition(k[1:-1])
P = X.take(I)
def worker(i):
s = slice(k[i], k[i+1])
I[s].take(P[s].argsort(), out=I[s])
t_list = []
for i in range(nproc):
t = Thread(target=worker, args=(i,))
t.start()
t_list.append(t)
for t in t_list:
t.join()
return I