使用python多处理“桶排序”

时间:2014-07-04 14:37:58

标签: python sorting numpy parallel-processing multiprocessing

我有一个统一分布的数据系列。我希望利用分发来并行排序数据。对于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()

2 个答案:

答案 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)

在我看来,有两个主要问题。

  1. 多个进程的开销以及它们之间的通信

    产生一些Python解释器会产生一些开销,但主要是将数据传入和传出&#34; worker&#34;进程正在扼杀性能。您通过Queue传递的数据需要&#34;腌制&#34;和&#34; unpickled&#34;,这对于较大的数据来说有点慢(你需要这样做两次)。

    如果您使用线程而不是进程,则不需要使用Queue。在CPython中使用线程来处理CPU繁重的任务通常被认为效率低下,因为通常你会遇到Global Interpreter Lock,但并非总是如此!幸运的是,Numpy的排序功能似乎正在发布GIL,所以使用线程是一个可行的选择!

  2. 数据集的分区和连接

    对数据进行分区和加入是这种&#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也很有用。

  3. 总结一下,我能做到的最快的是以下内容(但它仍然没有像你想要的那样与内核数量成线性比例)。

    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