为什么单个任务在较快的群集上需要花费更长的时间?

时间:2019-11-21 17:17:05

标签: dask dask-distributed

简介

当我将工作提交到dask集群(分布式调度程序,future API)时,我发现任务花费的时间比在本地计算机上按顺序运行要长得多。在数量上,(已编辑)大约长30%。

我不认为这是由于任务之间的CPU争用造成的,因为加起来后,内部时间少于num_workers * end_to_end_time

当任务不需要任何输入时,我也可以重现效果,因此它不是由于序列化或在集群中移动数据所致。

最后,我观察到,我的敏捷集群中的工人人数越多,效率的这种“损失”就越严重。

示例

在以下示例中,我正在任务内部使用time.monotonic测量任务的持续时间,并将该持续时间作为任务的结果返回。我为每个任务提交了固定数量的工作,在本地运行时,每个任务大约需要0.1s。我正在使用pure=False提交工作,以便使dask不会缓存结果。我正在使用的dask群集有10个单线程工作程序,每个工作程序有1个cpu和4Gb内存。

在打印的摘要中,当顺序运行和快速运行时,我将打印内部时间和端到端时间。我还将打印时间作为连续时间的百分比进行比较。最后,我打印了一份“黄昏”的“开销”,方法是将工时数乘以端到端时间,然后减去对工时测得的内部时间。产生这种开销的事实表明,我在任务之间没有CPU争用(尽管我仍然可以在后台争用其他东西)。

import time

import dask.distributed


def main():
    num_tasks = 350

    time_internal_seq, time_end_to_end_seq = time_work_sequential_but_on_dask(num_tasks)
    print()
    print("Running sequentially")
    print(f"Internal time: {time_internal_seq:.1f}s (average of {time_internal_seq / num_tasks:.2f}s per task)")
    print(f"End-to-end time: {time_end_to_end_seq:.1f}s")

    time_internal_dask, time_end_to_end_dask, num_workers = time_work_dask(num_tasks)
    dask_overhead = num_workers * time_end_to_end_dask - time_internal_dask
    print()
    print(f"Running on dask ({num_workers} single threaded workers)")
    print(f"Internal time: {time_internal_dask:.1f}s ({time_internal_dask/time_internal_seq:.0%})")
    print(f"End-to-end time: {time_end_to_end_dask:.1f}s ({time_end_to_end_dask/time_end_to_end_seq:.0%})")
    print(f"Dask overhead: {dask_overhead:.1f}s ({dask_overhead/num_workers:.1f}s / worker)")


def time_work_sequential_but_on_dask(num_tasks):
    """Submit one large task running the loop sequentially."""
    with dask.distributed.Client(address="127.0.0.1:8786") as client:
        future = client.submit(time_work_sequential, num_tasks, pure=False)
        return future.result()


def time_work_sequential(num_tasks):
    start = time.monotonic()

    internal_times = []
    for i in range(num_tasks):
        delay = do_work()
        internal_times.append(delay)

    end = time.monotonic()
    end_to_end_time = end - start

    return sum(internal_times), end_to_end_time


def time_work_dask(num_tasks):
    """Submit lots of small tasks to dask."""
    with dask.distributed.Client(address="127.0.0.1:8786") as client:
        num_workers = len(client.scheduler_info()["workers"])

        start = time.monotonic()

        times_futures = []
        for i in range(num_tasks):
            delay_future = client.submit(do_work, pure=False)
            times_futures.append(delay_future)

        internal_times = client.gather(times_futures)

        end = time.monotonic()
        end_to_end_time = end - start

        return sum(internal_times), end_to_end_time, num_workers


def do_work():
    start = time.monotonic()

    approx_seconds = 0.1
    n = int(approx_seconds * 30_000_000)
    tot = 0
    for i in range(n):
        tot += 1

    end = time.monotonic()
    delay = end - start
    return delay


if __name__ == "__main__":
    main()

这是示例输出(已更新-请参见下面的编辑说明)

Running sequentially
Internal time: 59.7s (average of 0.17s per task)
End-to-end time: 59.7s

Running on dask (10 single threaded workers)
Internal time: 76.6s (128%)
End-to-end time: 8.1s (14%)
Dask overhead: 3.9s (0.4s / worker)

版本

我正在使用daskdask.distributed版本2.4.0和python版本3.6.8。

编辑(2019-11-26)

我(尴尬地!)意识到,我以前在笔记本电脑上按顺序运行的CPU明显优于我在黄昏时运行的云中的CPU。我仍然看到无法解释的开销,但现在是+ 30%,而不是+ 110%。我已经更新了上面的结果以反映这一点。

更新(2019-11-27)

我做了很多实验,研究了集群中的工人数量以及任务的时长。我有以下两个观察结果:

  1. 将更多工人添加到dask集群中会降低他们的效率
  2. 增加任务时间不会影响效率

1:增加更多工作人员会降低效率

在具有1个工作线程的集群上运行上述实验与在具有10个工作线程的集群上运行该实验:

 1 DASK WORKER
########################################################
Running sequentially
Internal time: 57.4s (average of 0.16s per task)
End-to-end time: 57.4s

Running on dask (1 single threaded workers)
Internal time: 56.7s (99%)
End-to-end time: 57.2s (100%)
Dask overhead: 0.6s (0.6s / worker)
########################################################

10 DASK WORKERS
########################################################
Running sequentially
Internal time: 70.9s (average of 0.20s per task)
End-to-end time: 70.9s

Running on dask (10 single threaded workers)
Internal time: 84.7s (119%)
End-to-end time: 8.9s (13%)
Dask overhead: 4.4s (0.4s / worker)
########################################################

在这里我们看到:

  1. 有10名敏捷工作者,单独提交任务而不是一项大任务时,效率损失约20%。
  2. 在有10名敏捷工人的情况下,执行一项大的连续任务所花费的时间大约要长20%(57.4s-> 70.9s)

2:增加任务的长度

在dask文档中的几个地方,建议确保任务不要太小。例如:

  

如果您的函数运行速度超过100毫秒左右,那么使用分布式计算可能不会带来任何提速。

https://distributed.dask.org/en/latest/efficiency.html#use-larger-tasks

  

为获得最佳性能,任务持续时间应大于10-100ms。

https://distributed.dask.org/en/latest/limitations.html#performance

  

Dask中的每个任务(一个单独的Python函数调用)的开销约为200微秒。因此,如果这些任务各花费1秒,那么Dask可以在计划开销支配成本之前达到大约5000个内核

https://docs.dask.org/en/latest/institutional-faq.html#how-well-does-dask-scale-what-are-dask-s-limitations

在我的示例中,任务在Google云中的VM上大约需要200毫秒,而我在使用的核数几乎没有达到5000。确实,我最多只能使用10名工人,只有350个任务。因此,我不希望上述实验会受到上述警告的影响。但是,为了显式地检查这一假设,我将虚拟do_work函数进行了10倍的迭代,因此需要10倍的时间。 结果显示,在运行一个大任务和运行许多小任务之间,效率下降的幅度完全相同。

Running sequentially
Internal time: 709.9s (average of 2.03s per task)
End-to-end time: 709.9s

Running on dask (10 single threaded workers)
Internal time: 852.6s (120%)
End-to-end time: 86.9s (12%)
Dask overhead: 16.2s (1.6s / worker)

0 个答案:

没有答案