如何并行化一个简单的Python循环?

2024-04-20 00:52:22 发布

您现在位置:Python中文网/ 问答频道 /正文

这可能是一个很小的问题,但是如何在python中并行化下面的循环呢?

# setup output lists
output1 = list()
output2 = list()
output3 = list()

for j in range(0, 10):
    # calc individual parameter value
    parameter = j * offset
    # call the calculation
    out1, out2, out3 = calc_stuff(parameter = parameter)

    # put results into correct output list
    output1.append(out1)
    output2.append(out2)
    output3.append(out3)

我知道如何在Python中启动单个线程,但不知道如何“收集”结果。

多个进程也很好-无论什么是最简单的情况下。我目前正在使用Linux,但代码也应该在Windows和Mac上运行。

并行化这段代码最简单的方法是什么?


Tags: 代码foroutputparametersetupcalclistslist
3条回答

为了并行化一个简单的for循环,joblib为多处理的原始使用带来了很多价值。不仅是简短的语法,还有一些事情,比如当迭代非常快时(为了消除开销)透明地进行分组,或者捕获子进程的回溯,以获得更好的错误报告。

免责声明:我是joblib的原始作者。

What's the easiest way to parallelize this code?

我真的很喜欢concurrent.futures这个,在Python3since version 3.2中提供,并通过backport到PyPi上的2.6和2.7。

您可以使用线程或进程并使用完全相同的接口。

多处理

把这个放到一个文件-futuretest.py中:

import concurrent.futures
import time, random               # add some random sleep time

offset = 2                        # you don't supply these so
def calc_stuff(parameter=None):   # these are examples.
    sleep_time = random.choice([0, 1, 2, 3, 4, 5])
    time.sleep(sleep_time)
    return parameter / 2, sleep_time, parameter * parameter

def procedure(j):                 # just factoring out the
    parameter = j * offset        # procedure
    # call the calculation
    return calc_stuff(parameter=parameter)

def main():
    output1 = list()
    output2 = list()
    output3 = list()
    start = time.time()           # let's see how long this takes

    # we can swap out ProcessPoolExecutor for ThreadPoolExecutor
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for out1, out2, out3 in executor.map(procedure, range(0, 10)):
            # put results into correct output list
            output1.append(out1)
            output2.append(out2)
            output3.append(out3)
    finish = time.time()
    # these kinds of format strings are only available on Python 3.6:
    # time to upgrade!
    print(f'original inputs: {repr(output1)}')
    print(f'total time to execute {sum(output2)} = sum({repr(output2)})')
    print(f'time saved by parallelizing: {sum(output2) - (finish-start)}')
    print(f'returned in order given: {repr(output3)}')

if __name__ == '__main__':
    main()

结果如下:

$ python3 -m futuretest
original inputs: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
total time to execute 33 = sum([0, 3, 3, 4, 3, 5, 1, 5, 5, 4])
time saved by parallellizing: 27.68999981880188
returned in order given: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

多线程

现在将ProcessPoolExecutor更改为ThreadPoolExecutor,然后再次运行模块:

$ python3 -m futuretest
original inputs: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
total time to execute 19 = sum([0, 2, 3, 5, 2, 0, 0, 3, 3, 1])
time saved by parallellizing: 13.992000102996826
returned in order given: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

现在您已经完成了多线程和多处理!

注意性能和同时使用

采样太小,无法比较结果。

但是,我怀疑多线程处理通常比多处理快,特别是在Windows上,因为Windows不支持分叉,所以每个新进程都需要时间来启动。在Linux或Mac上,它们可能更接近。

您可以在多个进程中嵌套多个线程,但建议不要使用多个线程剥离多个进程。

由于全局解释器锁(GIL),在CPython上使用多个线程不会为纯Python代码提供更好的性能。我建议改用^{}模块:

pool = multiprocessing.Pool(4)
out1, out2, out3 = zip(*pool.map(calc_stuff, range(0, 10 * offset, offset)))

注意,这在交互式解释器中不起作用。

为了避免GIL中常见的FUD:无论如何,在这个例子中使用线程没有任何好处。您希望在这里使用进程,而不是线程,因为它们避免了一大堆问题。

相关问题 更多 >