Python dask:并行模型中的共享内存

Python dask:并行模型中的共享内存,python,pandas,dask,joblib,Python,Pandas,Dask,Joblib,我已经阅读了dask文档、博客等,但我仍然不完全清楚如何做到这一点。我的用例: 我有大约10GB的参考数据。一旦加载,它们是只读的。通常我们将它们加载到Dask/Pandas数据帧中 我需要这些ref数据来处理(丰富、修改、转换)每天大约5亿个事件(多个文件) “流程”是一个包含大约40项任务的管道。执行顺序是相关的(依赖项) 每个任务并不复杂或耗时,主要是查找、充实、映射等 事件之间没有依赖关系。理论上,我可以通过一个单独的线程处理每个事件,将输出合并到一个文件中,我就完成了。输出事件甚至不

我已经阅读了dask文档、博客等,但我仍然不完全清楚如何做到这一点。我的用例:

  • 我有大约10GB的参考数据。一旦加载,它们是只读的。通常我们将它们加载到Dask/Pandas数据帧中
  • 我需要这些ref数据来处理(丰富、修改、转换)每天大约5亿个事件(多个文件)
  • “流程”是一个包含大约40项任务的管道。执行顺序是相关的(依赖项)
  • 每个任务并不复杂或耗时,主要是查找、充实、映射等
  • 事件之间没有依赖关系。理论上,我可以通过一个单独的线程处理每个事件,将输出合并到一个文件中,我就完成了。输出事件甚至不需要与输入事件的顺序相同
总之:

  • 我们可以对事件处理进行大规模的视差化
  • 每个并行线程都需要相同的10GB(原始)ref数据
  • 处理单个事件意味着对其应用40个任务的序列/管道
  • 每个任务并不耗时(读取ref数据并修改事件)
可能的陷阱/问题:

  • 花更多的时间在序列化/反序列化上,而不是处理数据(我们在一些使用管道式方法的试验中确实遇到过这种情况)
  • ref数据被多次加载,每个(并行)进程加载一次
  • 我更愿意在我的笔记本电脑上开发/测试它,但是我没有足够的内存来加载ref数据。可能是解决方案是否会利用内存映射
最有效的解决方案似乎是,如果我们能够在内存中只加载一次ref数据,那么就可以使它以只读方式提供给处理事件的多个其他进程

通过在每台计算机中加载ref数据,扩展到多台计算机。将文件名推送到计算机上执行

你知道如何做到这一点吗


非常感谢您的帮助

一些您可以考虑的事情

  • 每个dask工作进程可以有任意数量的线程。线程间共享数据不需要复制,但进程间共享需要复制;因此,您应该尝试进程/线程组合,以找到最适合您的

  • 一般来说,最好将数据加载到工作进程中,而不是从客户端传递,即使在进程之间进行复制相当有效。如果您有足够的内存为每个worker持久化ref数据,那么这显然是最好的,尽管Dask尽其所能解释任务的常见中间依赖关系

  • 每个任务都会带来一些开销,并可能导致中间产品从一台机器移动到另一台机器。尽管某些线性流程链可能在优化时融合,但您最好编写一个函数,从函数中按顺序调用阶段,并将该函数作为单个任务调用,对于数据的每个部分调用一次

范例

f = client.submit(read_function, ref_filename)
out = client.map(process_function, list_of_inputs, ref=f)

其中,本例中的
process\u函数
采用一个输入(可能是元组)和
ref=
可选输入,即加载的ref数据。Dask将根据需要将参考数据复制到workers。

我找到了一个关于(python)Ray框架的方法。尽管Ray的业务目的非常不同,但它们面临着相同的核心需求:许多并行进程都利用只读共享内存数据帧。他们正在描述和解释他们为什么选择ApacheArrow和pyarrow。听起来很有趣,我们将在我们的用例中尝试一下。

我也遇到过类似的问题,即运行令人尴尬的并行作业,这些作业都在同一个查找“引用”表中获取数据(或并行进程的每个实例所需的任何大内存只读变量。只要您处于遵循“写时复制”语义的环境中(例如linux),将查找表置于全局范围内总是非常有效,如下所述:

以下是一个简单的并行工作流:

from multiprocessing import Pool

# Load your reference data, do that only once 
# here in the parent process
my_ref_lookup = load_ref_data(your_data_file)

def your_parallel_function(my_file_path):
    my_new_data = load_data(my_file_path)
    # process my_new_data with some lookup in my_ref_lookup 
    # which is known from the parent process. 

    processed_data = do_stuff(my_new_data)

    # you could here write something on disk
    # and/or return the processed_data

    return processed_data

with Pool(processes = 5) as Pool:
   list_of_result = Pool.map(your_parallel_function, your_list_of_file_paths)
这里执行
您的\u parallel\u函数
将并行执行,例如5个工作进程,一次在
您的\u文件路径列表
中获取5个文件,并且所有子进程都可以访问
我的\u ref\u查找

在花了一段时间处理Dask和bag集合后,我从未发现过类似或更简单的行为。在我尝试使用Dask时,在全局范围内以这种方式共享的只读变量最终被需要它的尽可能多的工作人员复制,这导致内存爆炸并使我的内核崩溃。我从未见过在任何Dask文档。Dask文档中唯一与此相关的远程参考是关于避免全局状态:
但这显示了共享变量被延迟函数修改的情况,这与当前仅共享“只读”的问题不同数据。

查看joblib是否可以完成以下任务:。能够有效地使用共享内存与基于大numpy的数据结构的工作进程。此处的更多详细信息:问题是关于多台计算机。多台计算机不是核心问题。核心问题是:在并行处理事件的多个进程之间共享只读引用数据-我们使用的是SDD,我们的问题是CPU受限。由于GIL,线程没有选择-我们只想将文件名传递给工作进程。但所有工作进程都需要相同的ref数据。因此,使用共享只读内存的想法非常感谢您的评论。您可以从每个工作进程函数访问unix共享内存,如果您知道如何做,可以使用多个进程也可以共享内存,签出内存映射。是的,我知道,我是这么说的:它被称为共享内存(不是内存映射)只是ray上的简短旁注,如果感兴趣的话:它要求所有代码都从c内部输入到处理中