调查 joblib 减速

Investigating joblib slowdown

我正在尝试使用 joblib 并行执行自定义随机森林实施训练。

任务并行得令人尴尬,所以我认为使用 joblib 获得加速应该不会太难。

下面是一些示例代码:

class RandomForest(object):
    def __init__(self, settings, data):
        self.forest = [None] * settings.n_trees
        self.parallel = Parallel(n_jobs=settings.njobs, backend="threading")

    def fit(self, data, train_ids_current_minibatch, settings, param, cache):
        self.forest = self.parallel(
            delayed(_parallel_build_trees_batch)(
                i_t, data, train_ids_current_minibatch, settings, param, cache)
            for i_t, tree in enumerate(self.forest))

    def partial_fit(self, data, train_ids_current_minibatch, settings, param, cache):
        self.forest = self.parallel(
            delayed(_parallel_build_trees_partial)(
                tree, i_t, data, train_ids_current_minibatch, settings, param, cache)
            for i_t, tree in enumerate(self.forest))

但是,在批处理和增量情况下,使用多个作业时训练速度会慢得多。数据和缓存参数是包含(大)numpy 数组的字典,所以我想知道这是否是原因。

我尝试使用 multiprocessing.Pool 编写相同的代码,但结果更糟,因为没有使用 joblib 的 threading 后端,我假设是因为拟合函数大量使用 numpy/scipy代码。

关于如何debug/fix减速有什么想法吗?

你的分析对我来说似乎是正确的:减速是由于 datacache 是大对象造成的。现在,在多处理环境中您没有共享内存,因此您需要以某种方式共享这些对象。 Python 通过 shared objects 支持:有一个 "main process" 真正持有该对象。但是其他进程需要通过某种机制发送所有更新(AFAIK对象被腌制然后通过管道或队列发送)这会减慢它的速度。

我看到一些适合你的选项:

  • 转换您的代码,使其使用 分区:我对随机森林不熟悉。我猜每个进程都有 data 作为初始数据集,然后你试图找到一个 "optimum"。如果您可以推动进程 1 找到所有 "type A" 最优值,并推动进程 2 找到所有 "type B" 最优值,然后让每个进程例如将他们的发现写入磁盘文件 rf_process_x.txt 那么你就不需要共享内存状态
  • 转换您的代码,使其使用 queues(参见 this page 上的最后一个示例):如果分区不起作用,那么也许您可以:
    1. 启动n个worker进程
    2. 每个进程都为自己建立 data 集合(因此它不在共享内存中)
    3. 在主进程中,你将 "jobs" 放入 task_queue,例如找到具有这组特定参数的随机森林。 worker 从 task_queue 获取工作,计算它并将其结果放在 result_queue 上。这只有在任务和结果很慢时才会很快,因为这些对象需要被 pickle 并通过管道从父进程发送到工作进程。
  • 使用joblibs memmappingJoblibs supports将对象转储到磁盘上,然后为每个对象提供对该文件的内存映射访问
  • 如果您的操作 CPU 绑定(执行繁重的磁盘或网络操作),您可以移动到 多线程 。这样你就真正拥有了共享内存。但据我所知,你 cpu 绑定并将 运行 放入 "GIL lock" 问题(在 cpython 中只有一个线程 运行 一次)
  • 您可能会发现其他加速随机森林的方法,例如提到了一些