对于CPU绑定任务,多线程Python程序比单线程程序更快



EDIT:原来这种奇怪的行为只发生在我的WSL ubuntu中的python上。否则,序列式确实比多线程式运行得更快。

我知道,对于CPython来说,通常情况下,多个线程只是在使用同一个CPU核心的情况下进行上下文切换,而不是像启动python解释器的多个实例那样使用多个CPU核心。

我知道,如果做得好的话,多线程对I/O绑定的任务很有好处。尽管如此,使用多线程时,CPU限制的任务实际上会更慢。因此,我用3个代码片段进行了实验,每个代码片段都进行了一些CPU限制的计算。

  • 示例1:按顺序运行任务(单线程(
  • 示例2:在不同线程中运行每个任务(多线程(
  • 示例3:在单独的进程中运行每个任务(多进程(

令我惊讶的是,尽管任务受CPU限制,但使用多个线程的示例2的执行速度(平均1.5秒(比使用单线程的示例1(平均2.2秒(更快。但是示例3的运行速度与预期的一样快(平均1秒(。

我不知道我做错了什么。

示例1:按顺序运行任务

import time 
import math
nums = [ 8, 7, 8, 5, 8]
def some_computation(n):
counter = 0
for i in range(int(math.pow(n,n))):
counter += 1
if __name__ == '__main__':
start = time.time()
for i in nums:
some_computation(i)
end = time.time()
print("Total time of program execution : ", round(end-start, 4) )

示例2:使用多线程运行任务

import threading
import time 
import math
nums = [ 8, 7, 8, 5, 8]
def some_computation(n):
counter = 0
for i in range(int(math.pow(n,n))):
counter += 1

if __name__ == '__main__':
start = time.time()
threads = []
for i in nums: 
x = threading.Thread(target=some_computation, args=(i,))
threads.append(x)
x.start()
for t in threads:
t.join()
end = time.time()
print("Total time of program execution : ", round(end-start, 4) )

示例3:与multiprocessing模块并行运行任务

from multiprocessing import Pool
import time
import math
nums = [ 8, 7, 8, 5, 8]
def some_computation(n):
counter = 0
for i in range(int(math.pow(n,n))):
counter += 1
if __name__ == '__main__':
start = time.time()
pool = Pool(processes=3)
for i in nums:
pool.apply_async(some_computation, [i])
pool.close()
pool.join()
end = time.time()
print("Total time of program execution : ", round(end-start, 4) )

原来这只发生在我安装在Windows Subsystem for Linux中的ubuntu中。我的原始代码段在Windows或Ubuntu python环境中按预期运行,但在WSL中没有,即顺序执行比多线程执行运行得更快。感谢@Vlad再次检查您的情况。

正如我在评论中所说,这是一个函数实际在做什么的问题。

如果我们使nums列表更长(即,将有更多的并发线程/进程(,并调整循环范围的计算方式,那么我们会看到:

import time 
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
nums = [8,7,8,5,8,8,5,4,8,7,7,8,8,7,8,8,8]
def some_computation(n):
counter = 0
for _ in range(n*1_000_000):
counter += 1
return counter
def sequential():
for n in nums:
some_computation(n)
def threaded():
with ThreadPoolExecutor() as executor:
executor.map(some_computation, nums)
def pooled():
with ProcessPoolExecutor() as executor:
executor.map(some_computation, nums)
if __name__ == '__main__':
for func in sequential, threaded, pooled:
start = time.perf_counter()
func()
end = time.perf_counter()
print(func.__name__, f'{end-start:.4f}')

输出:

sequential 4.8998
threaded 5.1257
pooled 0.7760

这表明some_computation((的复杂性决定了系统的行为方式。有了这段代码及其调整后的参数,我们可以看到线程处理比按顺序运行慢(正如人们通常预期的那样(,当然,多处理也比快得多

最新更新