如何有效地进行多进程处理读取不可变的大数据

时间:2013-12-17 17:29:07

标签: python multiprocessing

我想使用多处理来使用多个核来运行一个程序,该程序对大型列表中的元素进行成对比较:

data = [...] #when loaded this is > 100MB
for i in xrange(len(data)-1):
    parent = data[i]
    for j in xrange(i,len(data)):
        child = data[j]
        #do something with parent and child

所以如果我设置了一个进程队列:

def worker(queue):
    while True:
        args = queue.get()
        if args == 'EOF':
            break
        f(*args)

def f(data, x, start):
    for i in xrange(start,len(data)):
       #do stuff

if __name__ == '__main__':
    from multiprocessing import Process, Queue, cpu_count
    import psycopg2

    cur = psycopg2.connect(...).cursor()
    data = cur.execute('SELECT * from table') 
    #when loaded into memory data is > 100MB

    other_f_arg = 'some object'

    queue = Queue()
    #spawn 1 child per core:
    workers = [Process(target=worker, args=((queue,)) for cpu in xrange(cpu_count())]
    for w in workers:
        w.start()

    for i in xrange(len(data)-1):
        queue.put((data, other_f_arg, i))

    queue.put('EOF')
    for w in workers:
        w.join()

当它运行时,queue.put会在每次迭代时将data推送到队列中,即使数据只需要读取一次,然后只需重新引用每个进程。因此,重复数据传递抵消了multiproc的所有优点。如何让每个流程只需一次获取dataother_f_arg的副本,然后在工作人员被释放时只传递动态变量i

更新1:

我决定按照Tim Peters的建议使用Pool,但不是使用map,而是使用apply_async进行回调(因为我希望父进程执行一些操作)以串行方式对f的返回进行后处理,而不是等待所有提交完成(因为f也会在内存中返回大的内容):

def worker_init(xdata):
    global data
    data = xdata

def callback(result, x):
    #do something with result of f(i), and x

def f(i):
    #do something with data[i]
    return result

if __name__ == '__main__':
    ...
    data = psycopg2_cursor.fetchall()

    NUM_CPU = None
    from multiprocessing import Pool
    from functools import partial


    pool = Pool(processes=NUM_CPU,
            initializer=worker_init,
            initargs=(data,))

    x = 'some extra param I want to pass to callback'
    shim_callback = partial(callback, x=x)

    for i in xrange(len(data)-1):
        pool.apply_async(f,
                         args=(i,),
                         callback=shim_callback)

    pool.close()
    pool.join()

有没有办法将子节点中未捕获的异常重定向到控制台? (就像在单线程进程中引发的异常一样?)我问,因为f中未被捕获的异常似乎只是打破了调用apply_async的循环,并且我没有错误地输出到控制台或任何东西。

2 个答案:

答案 0 :(得分:2)

最简单:在Linux-y系统(支持fork()的操作系统)上,在模块级别定义data。然后,由于神奇的data语义,所有工作进程都会神奇地看到fork()的副本。

更具便携性:改为使用multiprocessing.Pool()。创建Pool时,可以指定要运行的初始化函数,以及传递给该函数的参数。然后,您可以在每个进程中仅将data传递给某个函数,例如,将其绑定到模块全局名称。然后,其他函数可以仅引用该模块全局。 Pool()还支持几种传递工作(和检索结果)的方法,这些方法不需要您明确地管理队列。这里没有足够的细节来说明你的具体问题是好还是坏。

充实“便携式”方式

这是一种方法:

NUM_CPU = None  # defaults to all available CPUs

def worker_init(xdata, xother_f_arg):
    global data, other_f_arg
    data = xdata
    other_f_arg = xother_f_arg

def f(start):
    for i in xrange(start, len(data)):
       #do stuff

if __name__ == '__main__':
    from multiprocessing import Pool
    import psycopg2

    cur = psycopg2.connect(...).cursor()
    data = cur.execute('SELECT * from table') 
    other_f_arg = 'some object'

    pool = Pool(processes=NUM_CPU,
                initializer=worker_init,
                initargs=(data, other_f_arg))
    pool.map(f, xrange(len(data) - 1))
    pool.close()
    pool.join()

请注意,它的代码也远远少于吊销您自己的队列。

虽然我无法运行您的代码,但我希望您最好不要使用data机器传递巨大的multiprocessing 而不是 worker从数据库加载自己的副本。沿着:

def worker_init(xother_f_arg):
    import psycopg2
    global data, other_f_arg
    other_f_arg = xother_f_arg
    cur = psycopg2.connect(...).cursor()
    data = cur.execute('SELECT * from table') 

编辑 - 处理错误

并行噱头很难在子进程(或线程)中引发异常,因为它们发生在上下文中 - 通常 - 与主程序当时正在做的事情无关。解决这个问题的最简单方法是保持对你正在创建的AsyncResult个对象的引用,并明确地.get()从它们得到结果(丢失回调!这只是无用的复杂化)。替换你的:

for i in xrange(len(data)-1):
    pool.apply_async(f,
                     args=(i,),
                     callback=shim_callback)

,例如,

# queue up all the work
futures = [pool.apply_async(f, args=(i,))
           for i in xrange(len(data) - 1)]
# retrieve results
for fut in futures:
    try:
        result = fut.get()
    except NameExceptionsYouWantToCatchHere as e:
        # do whatever you want with the exception
    else:
        # process result

来自docs(当前的Python 2):

  

get([timeout])

     

结果到达时返回结果。如果超时不是None而且    结果不会在超时秒内到达,然后引发multiprocessing.TimeoutError。如果远程调用引发了异常,那么get()将重新启动该异常。

在Python 3中,还有一个map_async()方法,以及许多error_callback方法的可选Pool()参数。

注意:如果len(data)我 非常大,multiprocessing机器可以消耗相当大量的RAM来排队所有工作项 - apply_async()永远不会阻塞,并且循环尽可能快地排队工作项。在这种情况下,可能需要另一层缓冲。

答案 1 :(得分:1)

问题是将“数据”传递给工作人员(=进程)会使数据被复制。因为它是一个相当大的数据集,你不会(即使你可以检查确认)有任何速度提升。

根据您拥有的数据类型,您应该检查多处理阵列http://docs.python.org/2/library/multiprocessing.html#multiprocessing.Array。它可能比'全球'更安全

您可以使用的代码类型是:

from multiprocessing import Process, Queue, cpu_count
import psycopg2
cur = psycopg2.connect(...).cursor()
data = cur.execute('SELECT * from table') 
#when loaded into memory data is > 100MB
shared_array = Array('your_data_type', data)

def worker(queue):
    while True:
       args = queue.get()
       if args == 'EOF':
           break
       f(*args)

def f(data, x, start):
    for i in xrange(start,len(data)):
      shared array[!!!!]#do stuff

if __name__ == '__main__':
    other_f_arg = 'some object'

    queue = Queue()
    #spawn 1 child per core:
    workers = [Process(target=worker, args=((queue,)) for cpu in xrange(cpu_count())]
    for w in workers:
        w.start()

    for i in xrange(len(data)-1):
        queue.put((data, other_f_arg, i))

    queue.put('EOF')
    for w in workers:
        w.join()