Python 达斯克';s并行for循环速度比单核慢

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

我尝试过的

我有一个令人尴尬的并行for循环,我在两个嵌套的for循环中迭代90x360个值并进行一些计算。我尝试了
dask.delayed
来按照并行化for循环,尽管它只针对一组非常小的迭代进行了演示

问题描述

我很惊讶地发现并行代码花了2小时39分钟,而非并行代码花了1小时54分钟,这意味着我在做一些根本错误的事情,或者任务图太大而无法处理

设置信息

这个测试是针对我迭代的一个子集进行的,即10 x 360,但是优化后的代码应该能够处理90 x 360嵌套迭代。我的小型集群有66个内核和256 GB的RAM,两个数据文件分别为4 GB和<1 GB。对于这项任务,
multi-processing
vs
multi-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将安排任何可用任务在可用时立即运行。有关令人尴尬的并行工作负载的更多信息,请参阅此示例: