Dask
介绍
Dask 是一个灵活的开源库,适用于 Python 中的并行和分布式计算。
什么是 Dask?
Dask 是一个开源库,旨在为现有 Python 堆栈提供并行性。Dask 与 Python 库(如 NumPy 数组、Pandas DataFrame 和 scikit-learn)集成,无需学习新的库或语言,即可跨多个核心、处理器和计算机实现并行执行。
- 用于并行列表、数组和 DataFrame 的 API 集合,可原生扩展 Numpy、NumPy、Pandas 和 scikit-learn,以在大于内存环境或分布式环境中运行。 Dask 集合是底层库的并行集合(例如,Dask 数组由 Numpy 数组组成)并运行在任务调度程序之上。
- 一个任务调度程序,用于构建任务图形,协调、调度和监控针对跨 CPU 核心和计算机的交互式工作负载优化的任务。
Dask 包含三个并行集合,即 DataFrame、Bag 和数组,每个均可自动使用在 RAM 和磁盘之间分区的数据,以及根据资源可用性分布在集群中多个节点之间的数据。对于可并行但不适合 Dask 数组或 DataFrame 等高级抽象的问题,有一个“延迟”函数使用 Python 装饰器修改函数,以便它们延迟运行。这意味着执行被延迟,并且函数及其参数被放置到任务图形中。
Dask 的任务调度程序可以扩展至拥有数千个节点的集群,其算法已在一些全球最大的超级计算机上进行测试。其任务调度界面可针对特定作业进行定制。Dask 可提供低用度、低延迟和极简的序列化,从而加快速度。
在分布式场景中,一个调度程序负责协调许多工作人员,将计算移动到正确的工作人员,以保持连续、无阻塞的对话。多个用户可能共享同一系统。此方法适用于 Hadoop HDFS 文件系统以及云对象存储(例如 Amazon 的 S3 存储)。
该单机调度程序针对大于内存的使用量进行了优化,并跨多个线程和处理器划分任务。它采用低用度方法,每个任务大约占用 50 微秒。
为何选择 Dask?
Python 的用户友好型高级编程语言和 Python 库(如 NumPy、Pandas 和 scikit-learn)已经得到数据科学家的广泛采用。
这些库是在大数据用例变得如此普遍之前开发的,没有强大的并行解决方案。Python 是单核计算的首选,但用户不得不为多核心或多计算机并行寻找其他解决方案。这会中断用户体验,还会让用户感到非常沮丧。
过去五年里,对 Python 工作负载扩展的需求不断增加,这导致了 Dask 的自然增长。Dask 是一种易于安装、快速配置的方法,可以加速 Python 中的数据分析,无需开发者升级其硬件基础设施或切换到其他编程语言。启动 Dask 作业所使用的语法与其他 Python 操作相同,因此可将其集成,几乎不需要重新写代码。
Demo
需要用到的依赖
pip install dask
pip install bokeh
pip install distributed
pip install dask[dataframe]
DataFrames
摘自官网 Dask DataFrames
你的数据太大了
你的计算太慢了,其他技术也不起作用
Pandas
您的数据很小
你的计算速度很快(亚秒)
有更简单的方法来加速你的计算,比如避免.application或Python for循环,并使用内置的pandas方法。
可以将一个DataFrame转换为一个Dask.DataFrame
import numpy as np
import pandas as pd
from dask.dataframe import dd
index = pd.date_range("2021-09-01", periods=2400, freq="1h")
df = pd.DataFrame({"a": np.arange(2400), "b": list("abcaddbe" * 300)}, index=index)
print(df)
ddf = dd.from_pandas(df, npartitions=10)
total = ddf["2021-10-01": "2021-10-09 5:00"].compute()
print(ddf)
print(total)
求和
import time
import dask.dataframe as dd
import pandas as pd
import tqdm
from dask.distributed import Client
from jy_common.jy_data import StockData
pd_1k = StockData.get_stock_trade_only_k_1m("2024-08-07", ['3'])
pd_1k_list = []
for x in tqdm.tqdm(range(10)):
pd_1k_list.append(pd_1k.copy())
#combined_df len 12857350 一千万数据
combined_df = pd.concat(pd_1k_list, ignore_index=True)
def pd_t():
print(f"combined_df len {len(combined_df)}")
ts = time.time()
# result = combined_df.groupby('wind_codes')['open'].sum()
result = combined_df.groupby('wind_codes')['open'].mean()
print(reult)
print('cost time :%s' % (time.time() - ts))
# cost time :1.0329430103302002
def dask_t():
# 可以指定集群 去掉则为本地模式
client = Client('192.168.10.5:8786',timeout=60)
# pip install dask[dataframe]
print(f"combined_df len {len(combined_df)}")
# npartitions 分区
ddf = dd.from_pandas(combined_df, npartitions=100)
ts = time.time()
# result = combined_df.groupby('wind_codes')['open'].sum()
result = ddf.groupby('wind_codes')['open'].mean().compute()
print(result)
print('cost time :%s' % (time.time() - ts))
# 本地
# 2 Work cost time :5.209286451339722
# 集群
# 2 Work npartitions=100 cost time :41.783215284347534
# 4 Work npartitions=100 cost time :21.400845766067505
# 6 Work npartitions=100 cost time :17.400845766067505
# 6 Work npartitions=1 cost time :9.316875219345093
if __name__ == '__main__':
dask_t()
Partitions
分区, 可以用npartitions指定分区个数,Dask DataFrame 被拆分为许多分区,其中每个分区是一个 Pandas DataFrame。这些 DataFrame 沿索引垂直拆分
ddf = dd.from_pandas(combined_df, npartitions=100)
在做一些特定操作时,合适的分区会影响操作的效率.分区也会再聚合时对需要做聚合的操作分成几分执行.
使用索引
以选择性地沿单个索引列对 Dask DataFrame 进行排序。针对此列的某些操作可能非常快。例如,如果数据集按时间排序,则可以快速选择特定日期的数据、执行时间序列联接等。您可以通过查看 df.known_divisions 属性来检查您的数据是否已排序。您可以使用 .set_index(column_name) 方法设置索引列。
避免全数据洗牌
某些操作(如 set_index 和 merge/join )在并行或分布式设置中比在单台计算机的内存中执行更难。特别是,重新排列数据的洗牌操作变得更加通信密集。例如,如果您的数据是按客户 ID 排列的,但现在您想按时间排列数据,则所有分区都必须相互通信以交换数据分片。这可能是一个密集的过程,尤其是在集群上。
结论
经测试在一千万数据,pandas做数据求均值, pandas更快,dask在本地模式和集群 2 4 6个Work时表现不同,但均慢于Pandas.其实大部分时间耗费在网络传输和集群交互.简单操作感觉不适用
Delayed
dask.delayed 接口并行化自定义算法
import time
from dask import delayed
def inc(x):
time.sleep(1)
return x + 1
def add(x, y):
time.sleep(1)
return x + y
def no_delayed():
"""
普通代码
time
no_delayed: 3.001702070236206
# 因为我们一个接一个地按顺序调用每个函数
@return:
"""
ts = time.time()
x = inc(1)
y = inc(2)
z = add(x, y)
print(f"no_delayed: {time.time() - ts}")
def dask_delayed():
"""
dask
time
dask_delayed: 2.5537471771240234
@return:
"""
ts = time.time()
x = delayed(inc)(1)
y = delayed(inc)(2)
z = delayed(add)(x, y)
# 这会立即运行,它所做的只是构建一个图
# 这里才事实上地使用本地线程池运行我们的计算
z.compute()
print(f"dask_delayed: {time.time() - ts}")
if __name__ == '__main__':
no_delayed()
dask_delayed()
分布式
本地模式
所有未指定集群的方式运行Dask代码都是本地模式运行
集群方式
注: 需要准备几台python环境相同,并且Dask包相同的电脑
简单环境搭建
主服务器执行
dask-scheduler
'''
2024-08-09 13:29:47,133 - distributed.scheduler - INFO - -----------------------------------------------
2024-08-09 13:29:47,759 - distributed.http.proxy - INFO - To route to workers diagnostics web server please install jupyter-server-proxy: python -m pip install jupyter-server-proxy
2024-08-09 13:29:47,791 - distributed.scheduler - INFO - State start
2024-08-09 13:29:47,818 - distributed.scheduler - INFO - -----------------------------------------------
2024-08-09 13:29:47,819 - distributed.scheduler - INFO - Scheduler at: tcp://192.168.10.5:8786
2024-08-09 13:29:47,819 - distributed.scheduler - INFO - dashboard at: http://192.168.10.5:8787/status
2024-08-09 13:29:47,819 - distributed.scheduler - INFO - Registering Worker plugin shuffle
'''
访问上边给出的Status地址可以看到监控页面,这里面可以看的运行的信息
其他电脑执行
# dask-worker 地址 上边执行后会有地址
dask-worker 192.168.10.211:8786
'''
2024-08-09 13:53:47,813 - distributed.nanny - INFO - Start Nanny at: 'tcp://192.168.10.212:37249'
2024-08-09 13:53:48,698 - distributed.diskutils - INFO - Found stale lock file and directory '/tmp/dask-scratch-space/worker-bo7nhiy2', purging
2024-08-09 13:53:48,698 - distributed.diskutils - INFO - Found stale lock file and directory '/tmp/dask-scratch-space/worker-8ykf0m9y', purging
2024-08-09 13:53:49,810 - distributed.worker - INFO - Start worker at: tcp://192.168.10.212:46488
2024-08-09 13:53:49,810 - distributed.worker - INFO - Listening to: tcp://192.168.10.212:46488
2024-08-09 13:53:49,811 - distributed.worker - INFO - dashboard at: 192.168.10.212:39052
2024-08-09 13:53:49,811 - distributed.worker - INFO - Waiting to connect to: tcp://192.168.10.211:8786
2024-08-09 13:53:49,811 - distributed.worker - INFO - -------------------------------------------------
2024-08-09 13:53:49,811 - distributed.worker - INFO - Threads: 8
2024-08-09 13:53:49,811 - distributed.worker - INFO - Memory: 7.62 GiB
2024-08-09 13:53:49,811 - distributed.worker - INFO - Local Directory: /tmp/dask-scratch-space/worker-v9srcqjw
2024-08-09 13:53:49,811 - distributed.worker - INFO - -------------------------------------------------
2024-08-09 13:53:54,624 - distributed.worker - WARNING - Mismatched versions found
+---------+---------------------------------------------+-----------+---------+
| Package | Worker-0a4056b4-36cd-4477-8f6d-8b45014cad76 | Scheduler | Workers |
+---------+---------------------------------------------+-----------+---------+
| lz4 | 4.3.3 | None | 4.3.3 |
+---------+---------------------------------------------+-----------+---------+
2024-08-09 13:53:54,625 - distributed.worker - INFO - Starting Worker plugin shuffle
2024-08-09 13:53:54,626 - distributed.worker - INFO - Registered to: tcp://192.168.10.211:8786
2024-08-09 13:53:54,626 - distributed.worker - INFO - -------------------------------------------------
2024-08-09 13:53:54,627 - distributed.core - INFO - Starting established connection to tcp://192.168.10.211:8786
'''
使用
任何dask代码前加上以下代码,运行代码时就会自动把Dask任务放到集群运行
from dask.distributed import Client
# 连接集群
client = Client('192.168.10.211:8786')
Future
Dask 支持一个实时任务框架,该框架扩展了 Python 的 concurrent.futures 接口。
集群
虚拟机2: 8处理器 8G内存 1 Work
虚拟机3: 8处理器 8G内存 2 Work
虚拟机4: 8处理器 8G内存 2 Work
import time
from dask.distributed import Client
def square(x):
# 让程序暂停5秒
time.sleep(5)
print(f'square--{x}')
return x ** 2
def neg(x):
# 让程序暂停5秒
time.sleep(5)
print(f'neg--{x}')
return -x
def no_dask():
"""
不用dask 单线程
#2个任务每个任务运行完需要5秒 10万次循环 cost time :1000000
"""
ts = time.time()
add_list1 = []
for e in range(100000):
add_list1.append(square(e))
add_list2 = []
for e2 in add_list1:
add_list2.append(neg(e2))
total = sum(add_list2)
print(total) # 输出: 15
print('cost time :%s' % (time.time() - ts))
def dask_cluster():
"""
集群
#5 Work 2个任务每个任务运行完需要5秒 10万次循环 cost time :25066.37527489662
"""
client = Client('192.168.10.211:8786')
ts = time.time()
# 执行 square
A = client.map(square, range(100000))
# 执行 neg
B = client.map(neg, A)
# 汇总结果
total = client.submit(sum, B)
print(total.result())
print('cost time :%s' % (time.time() - ts))
client.close()
def add(x):
print(x)
return x
def dask_single():
"""
单机dask
"""
client = Client()
ts = time.time()
A = client.map(square, range(100000))
B = client.map(neg, A)
# 汇总结果 可以传一个自定义函数对结果进行处理
total = client.submit(add, B)
print(total.result())
print('cost time :%s' % (time.time() - ts))
if __name__ == '__main__':
dask_single()
Other
中文翻译文档 www.heywhale.com/home/column…
Apache Parquet 是一种列式二进制格式。它是存储大量表格数据的事实标准,也是我们推荐的基本表格数据存储解决方案。
Kubernetes 是一个开源的容器编排引擎,用来对容器化应用进行自动化部署、扩缩和管理。该项目托管在 CNCF。
分布式计算哪家强:Spark、Dask、Ray大比拼-CSDN博客