您好,
我遇到了一个dask.array
示例,其中计算时间和所需内存在线程(共享内存)调度程序(dask.get
,dask.threaded.get
)和具有工作进程的调度程序({{ 1}},dask.multiprocessing.get
)
我在带有SSD的2013 Core i7 16GB macOS Macbook Pro上测试了这个设置。
手头的模拟实现了长度为distributed.Client().get
的多个ndims
向量之间的网格网格操作,然后对它们执行一些简单的元素运算,然后通过在每个维度中求和得到最终结果。没有dimlen
,示例就是这样,网格数组的副本将与dask
一样大。由于我们有8*(dimlen**ndims)/1024**3 = 7.4 GByte
个参数,如果所有参数都是通过网格参数的简单副本完成的,那么我们需要超过16 GB的RAM。 (顺便说一下:如果通过ndim = 3
接近示例,则numpy
和numpy.broadcast_to
无论如何都不会创建完整副本。仅在创建numpy.transpose
完整数组{时{1}}将被分配。)
到目前为止,据我所知,多处理和分布式调度程序的减速是由于大量的RAM消耗和一些写入磁盘的任务(在分布式诊断网页中看到)。但是我无法解释这种行为,因为我目前对要计算的dask图的理解是:
使用res
的想法是通过使用7.4 GByte
分块来减少内存需求。假设dask
,一个网格参数块块的副本的最大量为dask.array
。同样在缩小操作(一次一个维度)期间,当必须连接块块时,我们应该最终得到8*(chunklen**ndims)/1024**2 = 7.6 MByte
的最大块。假设我们有4个进程,那么在任何给定时间我们只需要提到的块大小的四倍。然而,资源监控显示,对于涉及各种过程的调度程序,总共燃烧了16GB RAM。
我希望能够更深入地解释我在这里错过了什么。
提前致谢Markus
float64
使用
创建的Conda环境8*(dimlen/chunklen)*(chunklen**ndims)/1024**2 = 76 MByte
Hello @ kakk11,
非常感谢你的回答和努力。我已经对它进行了进一步的调查,如果我将您的示例增加到我的问题大小,即import numpy as np
import dask.array as da
from dask import get as single_threaded_get
from dask.threaded import get as threaded_get
from dask.multiprocessing import get as multiprocessing_get
ndims = 3
dimlen = 1000
chunklen = 100
# Some input data, which usually would come elsewhere
xs = [np.random.randn(dimlen) for _ in range(ndims)]
# Cast them to dask.array
ys = [da.from_array(x, chunks=chunklen) for x in xs]
# Meshgrid
zs = [da.broadcast_to(y, ndims*(dimlen,)) for y in ys]
zs = [da.rechunk(z, chunks=ndims*(chunklen,)) for z in zs]
_a = tuple(range(ndims))
zs = [da.transpose(z, axes=_a[i:] + _a[:i]) for i, z in enumerate(zs)]
# Some simple element-wise processing of n dimensional arguments
res = zs[0]
for z in zs[1:]:
res = res + z
# Some reduction of all dimensions to a scalar
for i in range(ndims):
res = da.sum(res, axis=-1)
res
dask.array<sum-aggregate, shape=(), dtype=float64, chunksize=()>
len(list(res.dask.keys()))
#12617
%%timeit -n 1 -r 1
x = res.compute(get=single_threaded_get)
#10.4 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -n 1 -r 1
x = res.compute(get=threaded_get)
#7.32 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -n 1 -r 1
x = res.compute(get=multiprocessing_get)
#5h 14min 52s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
from distributed import Client
client = Client()
%%timeit -n 1 -r 1
x = res.compute(get=client.get)
#7min 37s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
,$conda create -n py35 python=3.5 dask distributed jupyter
$source activate py35
$jupyter notebook .
!conda list -e
# This file may be used to create an environment using:
# $ conda create --name <env> --file <this file>
# platform: osx-64
appnope=0.1.0=py35_0
bleach=1.5.0=py35_0
bokeh=0.12.5=py35_1
chest=0.2.3=py35_0
click=6.7=py35_0
cloudpickle=0.2.2=py35_0
dask=0.14.3=py35_0
decorator=4.0.11=py35_0
distributed=1.16.3=py35_0
entrypoints=0.2.2=py35_1
heapdict=1.0.0=py35_1
html5lib=0.999=py35_0
icu=54.1=0
ipykernel=4.6.1=py35_0
ipython=6.0.0=py35_1
ipython_genutils=0.2.0=py35_0
ipywidgets=6.0.0=py35_0
jedi=0.10.2=py35_2
jinja2=2.9.6=py35_0
jsonschema=2.6.0=py35_0
jupyter=1.0.0=py35_3
jupyter_client=5.0.1=py35_0
jupyter_console=5.1.0=py35_0
jupyter_core=4.3.0=py35_0
locket=0.2.0=py35_1
markupsafe=0.23=py35_2
mistune=0.7.4=py35_0
mkl=2017.0.1=0
msgpack-python=0.4.8=py35_0
nbconvert=5.1.1=py35_0
nbformat=4.3.0=py35_0
notebook=5.0.0=py35_0
numpy=1.12.1=py35_0
openssl=1.0.2k=2
pandas=0.20.1=np112py35_0
pandocfilters=1.4.1=py35_0
partd=0.3.8=py35_0
path.py=10.3.1=py35_0
pexpect=4.2.1=py35_0
pickleshare=0.7.4=py35_0
pip=9.0.1=py35_1
prompt_toolkit=1.0.14=py35_0
psutil=5.2.2=py35_0
ptyprocess=0.5.1=py35_0
pygments=2.2.0=py35_0
pyqt=5.6.0=py35_2
python=3.5.3=1
python-dateutil=2.6.0=py35_0
pytz=2017.2=py35_0
pyyaml=3.12=py35_0
pyzmq=16.0.2=py35_0
qt=5.6.2=2
qtconsole=4.3.0=py35_0
readline=6.2=2
requests=2.14.2=py35_0
setuptools=27.2.0=py35_0
simplegeneric=0.8.1=py35_1
sip=4.18=py35_0
six=1.10.0=py35_0
sortedcollections=0.5.3=py35_0
sortedcontainers=1.5.7=py35_0
sqlite=3.13.0=0
tblib=1.3.2=py35_0
terminado=0.6=py35_0
testpath=0.3=py35_0
tk=8.5.18=0
toolz=0.8.2=py35_0
tornado=4.5.1=py35_0
traitlets=4.3.2=py35_0
wcwidth=0.1.7=py35_0
wheel=0.29.0=py35_0
widgetsnbextension=2.0.0=py35_0
xz=5.2.2=1
yaml=0.1.6=0
zict=0.1.2=py35_0
zlib=1.2.8=3
,ndims = 3
,我会得到以下行为,多处理调度程序需要这样做“缓慢的解决方案”要长得多。但请记住,由于实际应用,我需要慢速解决方案的结构。
dimlen = 1000
此外,您可以尝试以下代码来查看两个版本的dask图中的差异。 chunklen = 100
仅包含直到最后的并行路径,而%%timeit -r 1 -n 1
fast_solution(x).compute(get=single_threaded_get)
# 2min 4s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -r 1 -n 1
fast_solution(x).compute(get=threaded_get)
# 49.5 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -r 1 -n 1
fast_solution(x).compute(get=multiprocessing_get)
# 55.4 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -r 1 -n 1
slow_solution(x).compute(get=single_threaded_get)
# 2min 21s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -r 1 -n 1
slow_solution(x).compute(get=threaded_get)
# 56.6 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%%timeit -r 1 -n 1
slow_solution(x).compute(get=multiprocessing_get)
# 10min 31s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
具有更多金字塔结构。但是考虑到我对传递的单个块大小的想法,我不知道问题是什么。
fast_solution
你好@MRocklin,
谢谢你的回答。据我所知,一些计算图表具有较高的数据交换成本,而手头的示例属于此类别。我进一步简化了它,导致两个维度的网格网格等效操作,然后将维度总数减少到标量。也许这可以作为一个例子来更好地洞察问题,如果slow_solution
行为正常,示例具有较高的数据交换成本ndims = 2
dimlen = 1000
chunklen = 500
# ...
from dask.dot import dot_graph
dot_graph(fast_solution(x).dask)
dot_graph(slow_solution(x).dask)
调度程序的问题。 请注意,该示例仅应用于分析内存使用情况,而不是CPU性能。它不会为CPU产生足够的工作量,因为它是一个简化的示例。
我想强调,我之所以关注这个问题,是因为我有这样的愿景,即dask / distributed应该能够处理图形结构,其中间的总数组大小远远超过工作者累积的RAM,如果chunksizes和本地图形结构远低于工作者RAM容量。该示例实现了这种图形结构。
所以这是更新的jupyter笔记本:dask_distributed_RAM_usage_meshgrid_operation.ipynb.zip
期待有关该主题的更多讨论,并感谢您在此问题上的努力。
马库斯
答案 0 :(得分:1)
感谢有趣的测试。看起来multiprocessing_get
在列表总和方面存在问题,我只能猜测,为什么。默认情况下,dask.Bag中使用多处理,这是python对象的用例,而不是数组,并且在需要进程间通信时不能快速执行。
无论如何,当您对计算中的所有步骤使用dask函数时,它实际上在所有情况下都能快速运行,请参阅我的示例
import dask.array as da
from dask.multiprocessing import get as multiprocessing_get
import time
t0 = time.time()
ndims = 3
dimlen = 400
chunklen = 100
x = [da.random.normal(10, 0.1, size=ndims*(dimlen,), chunks=ndims*(chunklen,)) for k in range(ndims)]
def slow_solution(x):
res = x[0]
for z in x[1:]:
res = res + z
return da.sum(res)
def fast_solution(x):
return da.sum(da.stack(x))
t1 = time.time()
print("start fast f-n")
fast_solution(x).compute(get=multiprocessing_get)
t2 = time.time()
print("start slow f-n")
slow_solution(x).compute(get=multiprocessing_get)
t3 = time.time()
print("Whole script: ", t3 - t0)
print("fast function: ", t2 - t1)
print("slow function: ", t3 - t2)
<强>更新强>
是否有特殊原因需要使用multiprocessing_get
和threaded
不适合您,或者您只是好奇? dask的文档并不完全全面,但从我得到的,multiprocessing
解决方案通常用于dask Bag,这是任何类型的python对象的更通用的解决方案。它的性能存在已知限制,请参阅http://dask.pydata.org/en/latest/shared.html#known-limitations
答案 1 :(得分:0)
我怀疑你的计算会强制进行大量的数据交换,如果你在同一个进程中这是免费的,但如果你想使用不同的进程可能会很昂贵。这引入了两个成本:
dask调度程序通常会尝试计算允许它清理中间结果的任务,但这并不总是可行的。当我使用分布式调度程序web diagnostic dashboard在我的机器上运行计算时,我发现大部分时间都花在进程间通信上,并将数据溢出到磁盘并将其读回。
我还没有深入研究你的问题,以确定这是你的问题所固有的,还是dask安排事情的缺陷。如果您能够进一步简化计算,同时仍然显示相同的性能缺陷,那么这将使诊断更容易。