Python中的多处理和多线程
我有一个python程序,它1)从磁盘读取一个非常大的文件(大约95%的时间),然后2)处理并提供相对较小的输出(大约5%的时间)。该程序将在TB的文件上运行 现在我希望通过利用多处理和多线程来优化这个程序。我运行的平台是一个虚拟机,在一个虚拟机上有4个处理器 我计划有一个调度程序进程,它将执行4个进程(与处理器相同),然后每个进程都应该有一些线程,因为大部分是I/O。每个线程将处理1个文件,并将结果报告给主线程,主线程将通过IPC将结果报告给调度程序进程。调度器可以对这些数据进行排队,并最终按顺序将它们写入磁盘 所以,想知道如何确定为这种场景创建的进程和线程的数量?有没有一种数学方法可以计算出什么是最佳组合 感谢您的并行处理: 我看到并引用了公认的答案: 在实践中,可能很难找到最佳线程数,甚至在每次运行程序时,该线程数也可能会有所不同。因此,理论上,线程的最佳数量将是您机器上的内核数量。如果您的内核是“超线程”(Intel称之为“超线程”),那么它可以在每个内核上运行2个线程。那么,在这种情况下,线程的最佳数量是机器上内核数量的两倍 对于多处理: 有人问了一个类似的问题,被接受的答案是: 如果您的所有线程/进程确实都是CPU绑定的,那么您应该运行与CPU报告核心数量相同的进程。由于超线程,每个物理CPU核可能能够呈现多个虚拟核。调用Python中的多处理和多线程,python,multitasking,Python,Multitasking,我有一个python程序,它1)从磁盘读取一个非常大的文件(大约95%的时间),然后2)处理并提供相对较小的输出(大约5%的时间)。该程序将在TB的文件上运行 现在我希望通过利用多处理和多线程来优化这个程序。我运行的平台是一个虚拟机,在一个虚拟机上有4个处理器 我计划有一个调度程序进程,它将执行4个进程(与处理器相同),然后每个进程都应该有一些线程,因为大部分是I/O。每个线程将处理1个文件,并将结果报告给主线程,主线程将通过IPC将结果报告给调度程序进程。调度器可以对这些数据进行排队,并最终按
multiprocessing.cpu\u count
获取虚拟核的数量
如果只有1个线程中的p是CPU限制的,那么可以通过乘以p来调整该数字。例如,如果有一半的进程是CPU限制的(p=0.5),并且有两个CPU,每个CPU有4个内核和2个超线程,则应该启动0.5*2*4*2=8个进程
这里的关键是了解您使用的是哪台机器,从中,您可以选择接近最佳数量的线程/进程来分割代码的执行。我说的几乎是最优的,因为每次运行脚本时它都会有一点变化,所以很难从数学的角度预测这个最优值
对于您的特定情况,如果您的机器有4个内核,我建议您最多只创建4个线程,然后拆分它们:
- 1到主线程
- 3用于文件读取和处理
消息可以由4个线程处理。通过这种方式,您可以优化处理器之间的负载。我想我会按照与您所做相反的方式来安排它。也就是说,我将创建一个特定大小的线程池,负责生成结果。提交到此池的任务将作为参数传递给处理器池,工作线程可以使用该处理器池提交CPU绑定的部分工作。换句话说,线程池工作人员主要负责执行所有与磁盘相关的操作,并将CPU密集型工作交给处理器池 处理器池的大小应该是环境中的处理器数量。很难给出线程池的精确大小;这取决于在收益递减定律发挥作用之前,它能处理多少并发磁盘操作。这还取决于您的内存:池越大,占用的内存资源就越多,尤其是在必须将整个文件读入内存进行处理的情况下。因此,您可能需要对该值进行实验。下面的代码概述了这些想法。从线程池中获得的是I/O操作的重叠,这比仅使用小型处理器池时要大:
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from functools import partial
import os
def cpu_bound_function(arg1, arg2):
...
return some_result
def io_bound_function(process_pool_executor, file_name):
with open(file_name, 'r') as f:
# Do disk related operations:
. . . # code omitted
# Now we have to do a CPU-intensive operation:
future = process_pool_executor.submit(cpu_bound_function, arg1, arg2)
result = future.result() # get result
return result
file_list = [file_1, file_2, file_n]
N_FILES = len(file_list)
MAX_THREADS = 50 # depends on your configuration on how well the I/O can be overlapped
N_THREADS = min(N_FILES, MAX_THREADS) # no point in creating more threds than required
N_PROCESSES = os.cpu_count() # use the number of processors you have
with ThreadPoolExecutor(N_THREADS) as thread_pool_executor:
with ProcessPoolExecutor(N_PROCESSES) as process_pool_executor:
results = thread_pool_executor.map(partial(io_bound_function, process_pool_executor), file_list)
重要注意事项:
另一个简单得多的方法是只拥有一个处理器池,其大小大于您拥有的CPU处理器数量,例如25个。工作进程将执行I/O和CPU操作。即使您的进程比CPU多,但许多进程仍将处于等待状态,等待I/O完成,从而允许运行CPU密集型工作
这种方法的缺点是,创建N个进程的开销远远大于创建N个线程+少量进程的开销。但是,随着提交到池中的任务的运行时间越来越长,增加的开销在总运行时间中所占的百分比也越来越小。因此,如果您的任务不是琐碎的,那么这可能是一个合理的性能简化
更新:两种方法的基准
我对处理24个大小约为10000KB的文件的两种方法做了一些基准测试(实际上,这些文件仅处理了3个不同的文件,每个文件处理了8次,因此可能进行了一些缓存):
方法1(线程池+处理器池)
方法2(仅处理器池)
结果:
(我有8个核)
线程池+处理器池:13.5秒
仅处理器池:13.3秒
结论:首先,我会尝试一种更简单的方法,即使用处理器池来处理所有事情。现在棘手的一点是决定要创建的最大进程数,这是您原始问题的一部分,并且在所有
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from functools import partial
import os
from math import sqrt
import timeit
def cpu_bound_function(b):
sum = 0.0
for x in b:
sum += sqrt(float(x))
return sum
def io_bound_function(process_pool_executor, file_name):
with open(file_name, 'rb') as f:
b = f.read()
future = process_pool_executor.submit(cpu_bound_function, b)
result = future.result() # get result
return result
def main():
file_list = ['/download/httpd-2.4.16-win32-VC14.zip'] * 8 + ['/download/curlmanager-1.0.6-x64.exe'] * 8 + ['/download/Element_v2.8.0_UserManual_RevA.pdf'] * 8
N_FILES = len(file_list)
MAX_THREADS = 50 # depends on your configuration on how well the I/O can be overlapped
N_THREADS = min(N_FILES, MAX_THREADS) # no point in creating more threds than required
N_PROCESSES = os.cpu_count() # use the number of processors you have
with ThreadPoolExecutor(N_THREADS) as thread_pool_executor:
with ProcessPoolExecutor(N_PROCESSES) as process_pool_executor:
results = list(thread_pool_executor.map(partial(io_bound_function, process_pool_executor), file_list))
print(results)
if __name__ == '__main__':
print(timeit.timeit(stmt='main()', number=1, globals=globals()))
from concurrent.futures import ProcessPoolExecutor
from math import sqrt
import timeit
def cpu_bound_function(b):
sum = 0.0
for x in b:
sum += sqrt(float(x))
return sum
def io_bound_function(file_name):
with open(file_name, 'rb') as f:
b = f.read()
result = cpu_bound_function(b)
return result
def main():
file_list = ['/download/httpd-2.4.16-win32-VC14.zip'] * 8 + ['/download/curlmanager-1.0.6-x64.exe'] * 8 + ['/download/Element_v2.8.0_UserManual_RevA.pdf'] * 8
N_FILES = len(file_list)
MAX_PROCESSES = 50 # depends on your configuration on how well the I/O can be overlapped
N_PROCESSES = min(N_FILES, MAX_PROCESSES) # no point in creating more threds than required
with ProcessPoolExecutor(N_PROCESSES) as process_pool_executor:
results = list(process_pool_executor.map(io_bound_function, file_list))
print(results)
if __name__ == '__main__':
print(timeit.timeit(stmt='main()', number=1, globals=globals()))