列表的高效求和涉及在保持顺序的同时并行化加法过程。 目标是创建一个类似于树的层次结构,其中相邻节点不断添加,直到只剩下一个节点。 我尝试过 ProcessPoolExecutor 和 ThreadPoolExecutor,以追求在配备 8 个 CPU 的设备上获得最佳性能。
import concurrent.futures
import time
add_list = list(range(100))
temp_len = len(add_list)
loop_start = time.time()
max_node_num = 0
while temp_len > 1 :
is_odd = temp_len%2
group_len = int(temp_len/2 + is_odd)
group = [(2*i, 2*i+1) for i in range(group_len - is_odd)]
if is_odd == 1:
last = add_list[-1]
def group_cont(group):
add_item = add_list[group[0]] + add_list[group[1]]
return add_item
with concurrent.futures.ThreadPoolExecutor() as executor:
results = executor.map(group_cont, group)
# with concurrent.futures.ProcessPoolExecutor() as executor:
# results = executor.map(group_cont, group)
add_list = []
for result in results:
add_list.append(result)
if is_odd == 1:
add_list.append(last)
temp_len = group_len
loop_end = time.time()
print(f'Time: {round(loop_end-loop_start,3)}')
输出:时间:0.012
“对于 ProcessPoolExecutor 时间:0.271”
为什么ThreadPoolExecutor比ProcessPoolExecutor效率更高?如何让ProcessPoolExecutor更加高效?
为什么
比ThreadPoolExecutor
效率高很多?ProcessPoolExecutor
正如评论中所解释的,这是因为后者创建进程以及在进程之间复制数据的开销超过了前者相应的开销。 显然,是 20 倍左右。
尽管由于 GIL 问题,
ThreadPoolExecutor
版本中的线程很可能(一次)仅使用一个核心。
请注意,数据不是通过磁盘文件传输的。 它实际上是通过管道传输的(在当前的实现中)。 即便如此,序列化、写入、读取和反序列化一个小对象的开销比将引用从一个线程传递到另一个线程要大几个数量级。
如何让
更有效率?ProcessPoolExecutor
在此示例中,您很可能不能。
真正的问题是,这是测试并行性的一个坏例子1。 基本上,每个并行任务的工作量都是微不足道的。 即使在理想情况下,将每个任务分派到另一个线程并获取结果的开销也远远超过(理想情况下)使用多核可能带来的加速。
另一方面,如果任务执行的工作量大几个数量级,您可能会发现
ProcessPoolExecutor
版本更快。
1 - 将您的测试代码描述为“极其低效”是恰当的,IMO。
当您使用 executor.map 函数时: 对于 ProcessPoolExecutor.map 默认是 chunksize = 1 对于 ThreadPoolExecutor.map 参数被忽略。 增加块大小将加快该过程。
参见:https://docs.python.org/3/library/concurrent.futures.html “当使用 ProcessPoolExecutor 时,此方法将可迭代对象分割成许多块,并将其作为单独的任务提交到池中。这些块的(近似)大小可以通过将 chunksize 设置为正整数来指定。对于非常长的可迭代对象,使用与默认大小 1" 相比,较大的 chunksize 值可以显着提高性能