【问题标题】:Block-wise array writing with Python multiprocessing使用 Python 多处理进行逐块数组写入
【发布时间】:2018-05-04 08:04:18
【问题描述】:

我知道有很多关于类似问题的主题(例如How do I make processes able to write in an array of the main program?Multiprocessing - Shared ArrayMultiprocessing a loop of a function that writes to an array in python),但我就是不明白……抱歉再次提问。

我需要用一个巨大的数组做一些事情,并希望通过将它分成块并在这些块上运行我的函数来加速事情,每个块都在自己的进程中运行。问题是:从一个数组中“剪切”块,然后将结果写入一个新的公共数组。这是我到目前为止所做的(最小的工作示例;不要介意数组整形,这对于我的实际情况是必要的):

import numpy as np
import multiprocessing as mp

def calcArray(array, blocksize, n_cores=1):
    in_shape = (array.shape[0] * array.shape[1], array.shape[2])
    input_array = array[:, :, :array.shape[2]].reshape(in_shape)
    result_array = np.zeros(array.shape)
    # blockwise loop
    pix_count = array.size
    for position in range(0, pix_count, blocksize):
        if position + blocksize < array.shape[0] * array.shape[1]:
            num = blocksize
        else:
            num = pix_count - position
        result_part = input_array[position:position + num, :] * 2
        result_array[position:position + num] = result_part
    # finalize result
    final_result = result_array.reshape(array.shape)
    return final_result

if __name__ == '__main__':
    start = time.time()
    img = np.ones((4000, 4000, 4))
    result = calcArray(img, blocksize=100, n_cores=4)
    print 'Input:\n', img
    print '\nOutput:\n', result

我现在如何实现多处理,即设置多个内核,然后 calcArray 将进程分配给每个块,直到达到 n_cores


在@Blownhither Ma 的大力帮助下,代码现在看起来像这样:

import time, datetime
import numpy as np
from multiprocessing import Pool

def calculate(array):
    return array * 2

if __name__ == '__main__':
    start = time.time()
    CORES = 4
    BLOCKSIZE = 100
    ARRAY = np.ones((4000, 4000, 4))
    pool = Pool(processes=CORES)
    in_shape = (ARRAY.shape[0] * ARRAY.shape[1], ARRAY.shape[2])
    input_array = ARRAY[:, :, :ARRAY.shape[2]].reshape(in_shape)
    result_array = np.zeros(input_array.shape)
    # do it
    pix_count = ARRAY.size
    handles = []
    for position in range(0, pix_count, BLOCKSIZE):
        if position + BLOCKSIZE < ARRAY.shape[0] * ARRAY.shape[1]:
            num = BLOCKSIZE
        else:
            num = pix_count - position
        ### OLD APPROACH WITH NO PARALLELIZATION ###
        # part = calculate(input_array[position:position + num, :])
        # result_array[position:position + num] = part
        ### NEW APPROACH WITH PARALLELIZATION ###
        handle = pool.apply_async(func=calculate, args=(input_array[position:position + num, :],))
        handles.append(handle)
    # finalize result
    ### OLD APPROACH WITH NO PARALLELIZATION ###
    # final_result = result_array.reshape(ARRAY.shape)
    ### NEW APPROACH WITH PARALLELIZATION ###
    final_result = [h.get() for h in handles]
    final_result = np.concatenate(final_result, axis=0)
    print 'Done!\nDuration (hh:mm:ss): {duration}'.format(duration=datetime.timedelta(seconds=time.time() - start))

代码运行并真正启动了我分配的数字进程,但比仅使用“原样”循环的旧方法花费的时间要长得多(3 秒与 1 分钟相比)。这里一定少了点什么。

【问题讨论】:

    标签: python arrays multiprocessing


    【解决方案1】:

    核心函数是pool.apply_asynchandler.get

    我最近一直在研究相同的功能,并发现制作标准实用功能很有用。 balanced_parallel 以并行方式默默地将函数 fn 应用于矩阵 aassigned_parallel 在每个元素上显式应用函数。
    一世。我拆分数组的方式是np.array_split。您可以改用块方案。
    ii.我在收集结果时使用concat 而不是分配给空矩阵。没有共享内存。

    from multiprocessing import cpu_count, Pool
    
    def balanced_parallel(fn, a, processes=None, timeout=None):
        """ apply fn on slice of a, return concatenated result """
        if processes is None:
            processes = cpu_count()
        print('Parallel:\tstarting {} processes on input with shape {}'.format(processes, a.shape))
        results = assigned_parallel(fn, np.array_split(a, processes), timeout=timeout, verbose=False)
        return np.concatenate(results, 0)
    
    
    def assigned_parallel(fn, l, processes=None, timeout=None, verbose=True):
        """ apply fn on each element of l, return list of results """
        if processes is None:
            processes = min(cpu_count(), len(l))
        pool = Pool(processes=processes)
        if verbose:
            print('Parallel:\tstarting {} processes on {} elements'.format(processes, len(l)))
    
        # add jobs to the pool
        handler = [pool.apply_async(fn, args=x if isinstance(x, tuple) else (x, )) for x in l]
    
        # pool running, join all results
        results = [handler[i].get(timeout=timeout) for i in range(len(handler))]
    
        pool.close()
        return results
    

    在您的情况下,fn 将是

    def _fn(matrix_part): return matrix_part * 2
    result = balanced_parallel(_fn, img)
    

    跟进: 您的循环应如下所示以进行并行化。

    handles = []
    for position in range(0, pix_count, BLOCKSIZE):
        if position + BLOCKSIZE < ARRAY.shape[0] * ARRAY.shape[1]:
            num = BLOCKSIZE
        else:
            num = pix_count - position
        handle = pool.apply_async(func=calculate, args=(input_array[position:position + num, :], ))
        handles.append(handle)
    
    # multiple handlers exist at this moment!! Don't `.get()` yet
    results = [h.get() for h in handles]
    results = np.concatenate(results, axis=0)
    

    【讨论】:

    • 嗯,至少它似乎有效 :) 现在我必须理解它,给我一些时间来理解它,然后我会接受它。谢谢!
    • @s6hebern 对不起,如果它很长:) 我已经多次遇到这个问题,所以我决定做一个通用的解决方案。你可以只拿必要的代码
    • 鉴于我根据您的回答进行的编辑,您是否看到我遗漏的任何内容?对我来说,看起来代码根本没有效果
    • @s6hebern 在应用下一个工作之前你会得到一个工作的结果,从而迫使工作一个接一个地完成。只需申请所有工作,然后获得所有结果
    • @s6hebern 又名使用一个循环进行应用,另一个循环用于获取
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-18
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多