当我将工作提交到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)
我正在使用dask
和dask.distributed
版本2.4.0和python版本3.6.8。
我(尴尬地!)意识到,我以前在笔记本电脑上按顺序运行的CPU明显优于我在黄昏时运行的云中的CPU。我仍然看到无法解释的开销,但现在是+ 30%,而不是+ 110%。我已经更新了上面的结果以反映这一点。
我做了很多实验,研究了集群中的工人数量以及任务的时长。我有以下两个观察结果:
在具有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)
########################################################
在这里我们看到:
在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个内核
在我的示例中,任务在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)