Python 达斯克';s并行for循环速度比单核慢
我尝试过的 我有一个令人尴尬的并行for循环,我在两个嵌套的for循环中迭代90x360个值并进行一些计算。我尝试了Python 达斯克';s并行for循环速度比单核慢,python,multithreading,multiprocessing,dask,Python,Multithreading,Multiprocessing,Dask,我尝试过的 我有一个令人尴尬的并行for循环,我在两个嵌套的for循环中迭代90x360个值并进行一些计算。我尝试了dask.delayed来按照并行化for循环,尽管它只针对一组非常小的迭代进行了演示 问题描述 我很惊讶地发现并行代码花了2小时39分钟,而非并行代码花了1小时54分钟,这意味着我在做一些根本错误的事情,或者任务图太大而无法处理 设置信息 这个测试是针对我迭代的一个子集进行的,即10 x 360,但是优化后的代码应该能够处理90 x 360嵌套迭代。我的小型集群有66个内核和25
dask.delayed
来按照并行化for循环,尽管它只针对一组非常小的迭代进行了演示
问题描述
我很惊讶地发现并行代码花了2小时39分钟,而非并行代码花了1小时54分钟,这意味着我在做一些根本错误的事情,或者任务图太大而无法处理
设置信息
这个测试是针对我迭代的一个子集进行的,即10 x 360,但是优化后的代码应该能够处理90 x 360嵌套迭代。我的小型集群有66个内核和256 GB的RAM,两个数据文件分别为4 GB和<1 GB。对于这项任务,multi-processing
vsmulti-threading
的方法,我也感到困惑。我认为在多个进程中运行类似于joblib
default实现的并行循环将是一种方法,因为每个循环都在独立的网格点上工作。但是,这表明多线程
速度更快,如果没有GIL问题(我没有),应该首选它。因此,对于上面的计时,我使用了dask.delay
default调度选项,它对单个进程使用多线程选项
简化代码
import numpy as np
import pandas as pd
import xarray as xr
from datetime import datetime
from dask import compute, delayed
def add_data_from_small_file(lat):
""" for each grid-point, get time steps from big-file as per mask, and
compute data from small file for those time-steps
Returns: array per latitude which is to be stacked
"""
for lon in range(0,360):
# get time steps from big file
start_time = big_file.time.values[mask1[:, la, lo]]
end_time = big_file.time.values[[mask2[:,la,lo]]
i=0
for t1, t2 in zip(start_time, end_time):
# calculate value from small file for each time pair
temp_var[i] = small_file.sel(t=slice(t1, t2)).median()
i=i+1
temp_per_lon[:, lon] = temp_var
return temp_per_lon
if __name__ == '__main__':
t1 = datetime.now()
small_file = xr.open_dataarray('small_file.nc') # size < 1 GB, 10000x91
big_file = xr.open_dataset('big_file.nc') # size = 4 GB, 10000x91x360
delayed_values = [delayed(add_data_from_small_file)(lat) for lat in range(0,10)] # 10 loops for testing, to scale to 90 loops
# have to delay stacking to avoid memory error
stack_arr = delayed(np.stack)(delayed_values, axis=1)
stack_arr = stack_arr.compute()
print('Total run time:{}'.format(datetime.now()-t1))
将numpy导入为np
作为pd进口熊猫
将xarray作为xr导入
从日期时间导入日期时间
从dask导入计算,延迟
def从小型文件(lat)添加数据:
“”“对于每个网格点,根据掩码从大文件中获取时间步长,然后
从这些时间步长的小文件计算数据
返回:要堆叠的每个纬度的数组
"""
对于范围内的lon(0360):
#从大文件中获取时间步长
start_time=big_file.time.values[mask1[:,la,lo]]
end_time=big_file.time.values[[mask2[:,la,lo]]
i=0
对于zip中的t1、t2(开始时间、结束时间):
#从小文件计算每个时间对的值
temp_var[i]=small_file.sel(t=slice(t1,t2)).median()
i=i+1
温度每分钟[:,分钟]=温度变量
每分钟返回温度
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
t1=datetime.now()
small_file=xr.open_数据数组('small_file.nc')#大小<1 GB,10000x91
big_file=xr.open_数据集('big_file.nc')#大小=4 GB,10000x91x360
延迟_值=[延迟(从_小_文件中添加_数据_)(lat)用于范围(0,10)内的lat)]#用于测试的10个循环,以缩放到90个循环
#必须延迟堆叠以避免内存错误
堆栈_arr=延迟(np.stack)(延迟_值,轴=1)
stack\u arr=stack\u arr.compute()
打印('总运行时间:{}'。格式(datetime.now()-t1))
每个延迟的任务都会增加大约1ms的开销。因此,如果您的函数速度慢(可能是调用其他昂贵的函数),那么dask.delayed可能是一个很好的选择。如果不是,那么您可能应该到别处看看
如果你想知道线程或进程是否更适合你,最简单的方法就是两者都尝试一下,这很容易做到
dask.compute(*values, scheduler="processes")
dask.compute(*values, scheduler="threads")
这可能是因为,即使您使用的是numpy数组,您的大部分时间实际上都花在Python for循环上。如果是这样,多线程在这里对您没有帮助,真正的解决方案是停止使用Python for循环,或者通过巧妙地使用numpy/xarray,或者通过使用像NUBA这样的项目。是否可以实现
[delayed(f)(x)对于范围(10)中的x,使用不同的进程/工作者同时并行运行x的所有迭代。例如,对于joblib
,我使用这种方法获得了显著的速度提升;并行(n_作业=10)(对于范围(0,10)中的x,延迟(f)(x))
。我尝试了dask.distributed
多一点,在我所有的尝试中,我看到大多数工作人员都在等待f(x)计算将在第一次迭代中完成,然后只进行x的下一次迭代。对于每个迭代,不同线程中有大量空任务流。默认情况下,Dask将安排任何可用任务在可用时立即运行。有关令人尴尬的并行工作负载的更多信息,请参阅此示例: