Python多进程:map和imap有什么区别?

64

我正在尝试学习如何使用Python的多进程包,但是我不理解mapimap之间的区别。

map返回一个实际的数组或集合,而imap返回一个对数组或集合的迭代器,这是否是它们之间的区别?我应该在什么情况下使用其中之一?

此外,我不明白chunksize参数是什么意思。这是传递给每个进程的值的数量吗?


4个回答

55

这就是它们的区别。你可能使用 imap 而不是 map 的原因之一是如果你想要在计算剩余结果之前开始处理前几个结果。map 会等待所有的结果才返回。

至于 chunksize,有时以较大的数量分配任务会更有效率,因为每次工作进程请求更多任务时,都需要进行 IPC 和同步开销。


1
那么,如何确定chunksize的合理值呢?如果更大意味着由于pickling而减少IPC和同步开销,那么会有什么权衡?(即为什么选取“chunksize == len(iterable)”是一个不好的想法?还是有其他原因?) - Adam Parkin
2
如果您选择 chunksize = len(iterable),那么所有作业都将分配给单个进程!len(iterable) // numprocesses 是最有用的最大值。权衡是同步开销和 CPU 利用率之间的关系(大的块大小会导致一些进程在其他进程之前完成,浪费潜在的处理时间)。 - Antimony
好的,我明白了,但这只意味着在特定数据和特定环境中选择合理的块大小归结为试错。 - Adam Parkin
我认为是这样的。大多数优化需要进行分析和微调。 - Antimony
1
值得一提的是,imap可以应用于生成器,而map会将您的生成器转换为类似列表的对象,因此imap不需要等待输入被生成。 - mgoldwasser
这是否意味着 imap 会从正在运行的 x 个进程中返回第一个结果?即它是否保留顺序?这是否意味着在迭代结果之前仍然必须等待第一个进程完成? - Tjorriemorrie

5

使用imap时,forked调用是并行进行的,而不是一个接一个地顺序进行。 例如,下面你需要访问三个交易所以获取订单簿信息。 与其按顺序依次访问交易所1,2,3,imap.pool调用是非阻塞的,并直接访问所有三个交易所来获取订单簿信息,只要你一调用就可以。

from pathos.multiprocessing import ProcessingPool as Pool
pool = Pool().imap
self.pool(self.getOrderBook, Exchanges, Tickers)

5

imap函数来自itertools模块,用于在Python中实现高效的内存使用。map函数返回列表,而imap函数返回一个对象,该对象可以为每个迭代生成值(在Python 2.7中)。下面的代码块将清楚地说明它们之间的区别。

map函数返回的列表可以直接打印输出。

 from itertools import *
    from math import *

    integers = [1,2,3,4,5]
    sqr_ints = map(sqrt, integers)
    print (sqr_ints)

IMAP返回的是一个对象,该对象被转换为列表并打印出来。

from itertools import *
from math import *

integers = [1,2,3,4,5]
sqr_ints = imap(sqrt, integers)
print list(sqr_ints)

Chunksize参数会将可迭代对象分成指定大小的块(大约相等),每个块都会作为单独的任务提交。


3
虽然这个回答的精神是正确的,但请注意原始问题是询问多进程映射和imap。 - Jacob Jones

3
一个关键的区别是你的工作函数何时以及如何返回其结果。如果你使用你的工作函数进行副作用(创建文件等)并且不希望它返回任何东西,那么这对你没有影响。
from multiprocessing import Pool
import time

start = time.time()

def get_time():
    return int(time.time() - start)

def worker(args):
    name, delay = args
    print(f'{get_time()}: Job {name} started ({delay} seconds)')
    time.sleep(delay)
    return f'Job {name} done'

jobs = [
    ('A', 1),
    ('B', 2),
    ('C', 10),
    ('D', 3),
    ('E', 4),
    ('F', 5),
]

if __name__ == '__main__':
    with Pool(2) as pool:
        for result in pool.map(worker, jobs):
            print(f'{get_time()}: {result}')

如果您使用 map,代码将生成以下输出:

 0: Job A started (1 seconds)
 0: Job B started (2 seconds)
 1: Job C started (10 seconds)
 2: Job D started (3 seconds)
 5: Job E started (4 seconds)
 9: Job F started (5 seconds)
14: Job A done
14: Job B done
14: Job C done
14: Job D done
14: Job E done
14: Job F done

正如您所看到的,所有工作都在第14秒以批量和输入顺序返回,而不管它们实际上何时完成。

如果您将方法更改为imap,则代码将生成以下输出:

 0: Job A started (1 seconds)
 0: Job B started (2 seconds)
 1: Job C started (10 seconds)
 1: Job A done
 2: Job D started (3 seconds)
 2: Job B done
 5: Job E started (4 seconds)
 9: Job F started (5 seconds)
11: Job C done
11: Job D done
11: Job E done
14: Job F done

现在完整的代码再次在第14秒完成,但是一些作业(AB)在实际完成时就已经返回了。这种方法仍然保持输入顺序,因此即使(您可以计算出)作业DE在第5秒和第9秒完成,它们也不能提前返回-它们仍然必须等待长时间的作业C直到第11秒。

如果将方法更改为imap_unordered,则代码将生成以下输出:

 0: Job A started (1 seconds)
 0: Job B started (2 seconds)
 1: Job C started (10 seconds)
 1: Job A done
 2: Job D started (3 seconds)
 2: Job B done
 5: Job E started (4 seconds)
 5: Job D done
 9: Job F started (5 seconds)
 9: Job E done
11: Job C done
14: Job F done

现在所有的作业在完成后立即返回。输入顺序不保留。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接