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

时间:2018-11-17 12:03:13

标签: python pandas dask joblib

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

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

总结:

  • 我们可以对事件处理进行大规模的处理
  • 每个并行线程都需要相同的10 GB的(原始)引用数据
  • 处理单个事件意味着将40个任务的序列/流水线应用到它们上
  • 每个任务都不费时(读取参考数据并修改事件)

可能的陷阱/问题:

  • 花更多的时间进行序列化/反序列化,而不是处理数据(我们在一些使用类似管道的方法的试验中确实遇到过这种情况)
  • ref-data多次加载,每个(并行)进程一次加载
  • 最好是我想在笔记本电脑上进行开发/测试,但是我没有足够的内存来加载引用数据。解决方案是否可能会利用memory_maps?

最有效的解决方案似乎是,如果我们只能将ref-data加载到内存中一次,则使其对处理事件的其他多个进程具有只读权限

通过在每台计算机上加载引用数据来扩展到多台计算机。将文件名推送到计算机以执行。

有什么想法要实现吗?

非常感谢您的帮助

3 个答案:

答案 0 :(得分:1)

我也遇到过类似的问题,即运行尴尬的并行作业,这些作业都在同一查找“引用”表(或并行进程的每个实例所需的任何大内存只读变量)中获取数据。当您处于遵循“写时复制”语义的环境(例如linux)中时,将查找表放置在全局范围内始终可以非常有效地工作,这在下面很好地解释了: Shared-memory objects in multiprocessing

这是一个简单的并行工作流程:

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)

此处your_parallel_function的执行将在例如5个工作程序,一次在your_list_of_file_paths中提取5个文件,所有子进程都可以访问my_ref_lookup,而无需复制它们。

经过一段时间的Dask和手袋收藏,我再也没有发现类似或简单的行为。在我尝试使用Dask的过程中,只读变量在全局范围内以这种方式共享,最终被需要它的许多工作人员复制,这激增了内存并使内核崩溃。我从未在任何Dask文档中见过此案。 Dask文档中唯一与此远程相关的参考是关于避免全局状态:https://docs.dask.org/en/latest/delayed-best-practices.html#avoid-global-state 但这显示了共享变量被修改为延迟函数的情况,这与当前仅共享“只读”数据的问题不同。

答案 1 :(得分:0)

我找到了关于{python} Ray框架的blog post。尽管Ray的业务目的非常不同,但它们面临着相同的核心要求:许多并行流程利用了只读的共享内存数据帧。他们正在描述和解释为什么他们选择使用Apache Arrow和pyarrow。听起来很有趣,我们将尝试用例。

答案 2 :(得分:-1)

您可以考虑的一些事情

  • 每个dask worker进程可以具有任意数量的线程。线程之间共享数据不需要复制,但是进程之间共享则需要复制。因此,您应该尝试使用进程/线程组合来找到最适合您的

  • 通常最好将数据加载到工作线程中,而不要从客户端传递数据,即使在进程之间进行复制非常有效。如果您有足够的内存为每个工作人员保留参考数据,那显然是最好的,尽管Dask会尽力解决任务的常见中间依赖项。

  • 每个任务都会带来一些开销,并且可能导致中间件从一台计算机移动到另一台计算机。尽管在优化时可能会融合一些线性的过程链,但您最好编写一个函数,该函数从一个函数开始依次调用各个阶段,然后将该函数作为单个任务针对数据的每个部分调用一次。

示例

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

在此示例中,process_function接受一个输入(可能是一个元组),而ref=则接受一个可选输入,即已加载的引用数据。 Dask将根据需要将参考数据复制给工作人员。