想象你正在搬家:家里物品堆成了小山,而你只有一辆小货车和一个人——先搬沙发、再搬书柜、来回一趟趟跑,货箱塞满就再也装不下了(内存储存不足,程序崩溃报 MemoryError)。这时来了一家搬家公司:调度员先上门登记所有物品、画一张搬运流程图(惰性计算),把大木箱拆成一口口标好号的小箱子(DataFrame 分块),然后同时派三辆货车往新家运(多核并行),最后在新家按图纸重新组装(compute 汇总)。这台“搬家公司”就是 Dask。它用你熟悉的 Pandas 语法,把数据拆小、把任务画成图、把算力摊到多个核甚至多台机器上,让你处理远超内存的大表格时,不再重写代码、不再崩溃。

岗位关联

对 AI 从业者而言,Dask 是数据工程师的“第二把钥匙”:当 Pandas 因内存不足而崩溃时,它让你无缝扩展到多核与集群,守住大规模数据处理和特征工程的生产力底线。

目录
  1. 可视化演示
  2. 数据分块:大搬家先拆箱
  3. 惰性计算:先做计划再行动
  4. 任务图调度:调度员的心算图
  5. 多线程运行:一个仓库多工人
  6. 分布式集群:多仓库协同
  7. 实战应用:什么时候叫搬家公司
  8. 适用边界:别杀鸡用牛刀
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

Dask并行计算全流程演示 DataFrame 分块:大数据切分为多个分区,每个分区独立并行计算 任务图 DAG:惰性计算将操作记录为有向无环图,优化后统一调度执行 多 Worker 并行:调度器将任务分配给多个工作线程同时执行,提升吞吐量 T_parallel ≈ T_serial / N_workers (理想加速比) 分块+惰性图+并行调度三步实现大数据高效处理

数据分块:大搬家先拆箱

搬家第一天,搬家公司的小王看着门口一口两米见方的大木箱犯了难:电梯进不去,楼道不好走,好几个人一起抬也纹丝不动。老师傅笑了:拆成四口小箱子,每口贴上标签,一人扛一口,多轻松!Dask 干的就是这件事——把 Pandas 装不下的超大 DataFrame,按行切成多块较小的 DataFrame。每一块就像一个“小箱子”,Dask 管它叫分区(partition)。

在 Pandas 里,一个 DataFrame 是一整张表,必须整整齐齐放进内存。而在 Dask 中,一张逻辑上的大表被拆成若干个 Pandas DataFrame 分区,散落在内存、甚至多台机器的内存里。你操作的是“虚拟大表”,真正干活的是底下一个个小分区。Dask 提供了 npartitions 参数控制分区个数,也可以用 blocksize 指定每个分区大约多少字节。

读 CSV 时使用 read_csv(blocksize='64MB'),Dask 会边读边切,不会把整个文件一次性塞进内存。分区的行数并不固定,而是由块大小决定。合理设置分区数很关键:太多则调度开销变大,太少则并行度不足。

就像搬家师傅根据每口小箱子的大小估算需要几趟车,Dask 根据总行数与每区行数算出分几个区。分区一旦定好,后续的搬运计划才有得安排。

P=⌈NtotalRchunk⌉P = \left\lceil \frac{N_{\text{total}}}{R_{\text{chunk}}} \right\rceil
P 表示分区数,N_total 是总行数,R_chunk 是每个分区期望容纳的行数。向上取整保证最后不足一块的数据也单独成一区,不丢任何箱子。
  1. N_total(数据总行数)整张表共有多少行,相当于仓库里待搬物品的总箱数。
    NtotalN_{\text{total}}
  2. R_chunk(每区行数)每个分区想装多少行,相当于一辆小车装几箱。
    RchunkR_{\text{chunk}}
  3. 相除后向上取整除法有零头时向上进一,保证最后不足一整车的余数也有车可坐。
    ⌈NtotalRchunk⌉\left\lceil \frac{N_{\text{total}}}{R_{\text{chunk}}} \right\rceil

惰性计算:先做计划再行动

搬家公司接到活后,并不立刻把箱子往车上扔。老师傅会先拿着登记表,写下计划:1 号车先搬客厅,2 号车搬卧室,到了新家先上二楼再上一楼。整个计划就是一张纸,货车一步都没动。Dask 也一样:你对大表做的每个操作,比如 groupby、过滤、合并,都只是在“计划纸”上多记一笔,一笔都不真算。

这种“光记不做”的机制叫惰性计算(Lazy Evaluation)。Pandas 是调用即执行;Dask 是调用只构建计算图,直到你喊出那句咒语——compute(),它才按图上流程真正跑起来。好处是:Dask 可以在动手前优化执行顺序、合并重复计算,避免白白搬运数据。

例如 ddf.groupby('city')['amount'].mean() 之后,Dask 不会返回真正的数值表,而是一个挂着计算图的惰性对象。若想拿结果,必须执行 ddf_result.compute()。如果中间结果想留在内存里复用,可以用 persist(),相当于把部分箱子提前卸到新家,后面不用再搬第二次。

搬家公司不可能把计划纸一扔就完事,总得在某一天真正搬家。compute() 就是那句“开始搬!”——之前所有铅笔勾画,都在这一刻变成货真价实的劳动果实。

lazy:y=f(g(x))→compute()result\text{lazy}: y = f(g(x)) \xrightarrow{\text{compute()}} \text{result}
左边是“计划纸”:记下先执行 g(x)、再执行 f(·) 得到 y,但不算数;调用 compute() 之后,Dask 才沿着这张图真正算出数值结果。
  1. x(分块数据)原始大表被切成的小分区。
    xx
  2. g(x)(第一步变换)例如过滤空值,是第一个小任务。
    g(x)g(x)
  3. f(g(x))(第二步变换)例如分组求和,依赖上一步的结果。
    f(g(x))f(g(x))
  4. compute()(触发执行)让 Dask 沿着计算图完成所有步骤并返回实值。
    compute(y)→result\text{compute}(y) \rightarrow \text{result}

任务图调度:调度员的心算图

调度员老张手里的计划纸不是简单清单,而是一张流程图:沙发要先搬出客厅才能装车,鞋柜要等门廊腾空才能落地。每个任务画一个圈,任务之间的先后关系连一条线。这张图,就是 Dask 的任务图(Task Graph)。

Dask 把所有计算拆成一个个小任务(Task),任务之间有依赖关系,整体构成一个有向无环图(DAG)。图中的节点是“对某个分区做某个操作”,边是“谁先谁后”。调度器不会随便乱派活,而是优先派出那些“前置任务都已完成”的就绪任务,让尽量多的核同时忙起来。

DAG 的好处是:只要两个任务互不依赖,就可以并行执行;即使某个任务慢,也不阻塞其他分支。Dask 还会做任务融合(Fusion),把一条线性链上的多个小任务合并成一个大任务,减少调度往返。这就是为什么 Dask 能高效处理复杂流水线:它把大象切块、排队、并行,而不是盲目蛮干。

就像老张总先把“无牵无挂”的箱子派给空闲货车,Dask 调度器时刻盯着流程图,把就绪任务塞给空闲核心,让整条流水线塞得满满当当。

G=(V,E),Vready={v∈V∣pred(v)=∅}G = (V, E), \quad V_{\text{ready}} = \{ v \in V \mid \mathrm{pred}(v) = \emptyset \}
G 表示任务图:V 是所有任务节点,E 是依赖边。V_ready 表示没有任何前驱任务、可以立即开工的“就绪任务”,调度器优先把它们派给空闲的核。
  1. V(任务节点)每个小任务,例如“对分区 1 做过滤”。
    VV
  2. E(依赖边)任务之间的先后关系,从先序任务指向后序任务。
    EE
  3. pred(v)(前驱集合)任务 v 必须先完成的任务们。
    pred(v)\mathrm{pred}(v)
  4. V_ready(就绪任务)没有前驱、可以立即执行的任务集合。
    Vready={v∈V∣pred(v)=∅}V_{\text{ready}} = \{ v \in V \mid \mathrm{pred}(v) = \emptyset \}

多线程运行:一个仓库多工人

同一间仓库里,老张同时指挥三个装卸工:阿强搬客厅的箱子,阿珍搬卧室的,阿贵搬书房的。三人都守在自己那片区域,各搬各的互不添乱,整体效率一下子翻了三倍。这就是 Dask 本地模式的多线程并行。

Dask 默认在线程池(ThreadPool)里跑任务。Pandas 的许多底层操作是 C 语言写成的,计算时不会一直占着 Python 的全局锁(GIL),所以多线程能真并行;但如果你的 apply 里塞了一个纯 Python 自定义函数,GIL 就会让线程们“排队”,加速比大打折扣。

衡量并行收益有一个经典定律——阿姆达尔定律:加速比上限取决于可并行部分的比例。假设 80% 的操作可并行,4 核理论加速比只有约 2.5,而不是 4。因此想榨干多核:能用内置向量化操作就别写纯 Python 循环;非用自定义函数不可,就考虑改用进程调度器,让多个进程绕开 GIL。

三个装卸工能同时用力,前提是活儿本身能拆开而且互不抢道。Dask 多线程的快乐,同样建立在 Pandas 底层 C 函数让出 GIL 的基础之上——用对地方才真香。

S=1(1−P)+PNS = \frac{1}{(1 - P) + \dfrac{P}{N}}
S 是理论加速比上限。P 是可并行部分耗时占比,(1-P) 是串行部分占比,N 是并行使用的核心数。哪怕核再多,串行部分也会锁死加速天花板。
  1. P(可并行占比)可并行部分耗时占总耗时的比例,取值范围 0 到 1。
    PP
  2. 1-P(串行占比)无法并行的部分,是加速比的天花板。
    1−P1 - P
  3. N(核心数)并行使用的 CPU 核数。
    NN
  4. S(加速比)理论最大提速倍数,永远小于 1/(1-P)。
    S=1(1−P)+P/NS = \frac{1}{(1 - P) + P / N}

分布式集群:多仓库协同

仓库不够用了怎么办?搬家公司租下城东、城西、城北三个仓库,把箱子分到三处,每辆货车只从最近的仓库装货。调度员老张坐镇总部,用对讲机远程指挥。活儿是大了,可对讲机喊来喊去也要花时间——传一句话的功夫,工人可能早就搬完一箱了。

Dask 的分布式集群(distributed)就是这种多仓库模式:一个 scheduler 节点(总部调度员)加多个 worker 节点(各仓库工人)。数据不再集中在单机内存,而是分片存放在多台机器的内存里。scheduler 会把任务派给“数据所在的那台机器”,省去搬数据的网络传输,这叫数据本地性。

跨机器的网络传输是分布式最大的开销。如果每个任务都要把几 GB 数据从 A 机器传到 B 机器,再快的计算也白搭。所以 Dask 设计上尽量让计算跟着数据走;只有 shuffle 类操作(如全局排序、按列重分区)才不得不大规模传输数据。Dask 的 dashboard 能实时看到每个 worker 的 CPU、内存和网络流量——就像总部的大屏幕,哪辆车堵了、哪辆车空着,一目了然。

对讲机喊三句能指挥完的活,就别喊三十句。Dask 让计算尽量贴近数据,道理和“就近仓库装货”一模一样——省下的每一分钟网络传输,都是实打实的提速。

Ttotal≈Tcompute+TnetworkT_{\text{total}} \approx T_{\text{compute}} + T_{\text{network}}
集群任务的总耗时,基本等于各 worker 真正计算的时间,加上调度和数据传输的通信时间。优化的核心目标就是让通信时间远小于计算时间。
  1. T_compute(计算耗时)各 worker 真正计算数据的时间。
    TcomputeT_{\text{compute}}
  2. T_network(通信耗时)调度、传输分片数据耗费的时间。
    TnetworkT_{\text{network}}
  3. 优化目标(通信远小于计算)让网络不再是瓶颈。
    Tnetwork≪TcomputeT_{\text{network}} \ll T_{\text{compute}}

实战应用:什么时候叫搬家公司

什么时候该请搬家公司?你心里已经有数:东西塞满整个客厅、连下脚的地方都没有的时候。对应到数据世界,就是 Pandas 开始报 MemoryError、或者内存占用飙到可用内存一半以上的时候。

Dask 的杀手锏首先是“装得下”:read_csv 支持 blocksize 参数,分块读入大文件;DataFrame 操作在分区上完成,内存峰值远小于全量加载。其次才是“跑得快”:多核或集群把计算摊开。对 AI 工程师来说,最典型的场景是 GB 级日志清洗、TB 级特征工程流水线,以及训练前的数据聚合准备。

迁移路径通常三步:第一步,把 pd.read_csv 换成 dd.read_csv,groupby、merge、fillna 等操作习惯保留;第二步,把结果 .compute() 转回 Pandas 交给下游;第三步,数据再大就上 Distributed 集群。Dask 的 API 与 Pandas 高度相似,很多脚本只需改动寥寥几行。但要注意:不是所有 Pandas 方法 Dask 都支持,遇到不支持的先查文档再换思路。

搬家公司的名片上写着:“先估体积,再定方案。”Dask 的上手也是同一句话——先算算数据多大、内存多少,超过阈值再掏名片,不冤。

Mest=R×C×B≥0.5×RAM⇒考虑 DaskM_{\text{est}} = R \times C \times B \geq 0.5 \times \text{RAM} \Rightarrow \text{考虑 Dask}
M_est 是粗略内存估算,等于行数 × 列数 × 每格字节数。如果估算值超过可用内存的一半,就说明 Pandas 已经吃力,应该考虑 Dask。
  1. R(行数)数据表的行数。
    RR
  2. C(列数)数据表的列数。
    CC
  3. B(每格字节数)单个单元格平均字节数,float64 占 8 字节。
    BB
  4. 0.5 × RAM(经验阈值)估算内存过半,就该上 Dask。
    0.5×RAM0.5 \times \text{RAM}

适用边界:别杀鸡用牛刀

可搬家公司也有办砸的时候:你只是想挪动阳台上一盆花,打电话叫来三辆货车、十位工人,等他们到楼下,你早自己端过去了。Dask 就是这样一把“大炮”,打蚊子反而累。

Dask 的代价是调度与序列化开销:一个简单操作,Pandas 毫秒级返回;Dask 光建图、派活、收集结果就要几十毫秒。因此,数据远小于内存时,Pandas 完胜。此外,Dask 也不适合需要极低延迟的在线查询——它更像批处理引擎,而不是即时响应系统。

另一个隐性边界是 shuffle 类操作:Dask 做全局排序、分布式 groupby 合并时,要跨分区重排数据,可能把磁盘当内存(spill),速度断崖式下跌。如果业务是高频迭代式算法(比如反复遍历全表的自定义优化循环),Dask 的惰性模型会让代码绕来绕去。遇到这些场景,要么换专用引擎(如 Spark、数据库),要么重新设计算法。判断准则很简单:数据远小于内存用 Pandas;数据超内存、操作为批处理,才轮到 Dask。

聪明的搬家公司会在电话里先问:“您这有多少东西?”因为有些活,自己拎个袋子就办了。Dask 爱好者最该学会的,反而是“什么时候别用 Dask”。

选型={Pandas,Mdata≪MRAMDask,Mdata≳MRAM 且批处理其他引擎,低延迟或重度 shuffle\text{选型} = \begin{cases} \text{Pandas}, & M_{\text{data}} \ll M_{\text{RAM}} \\ \text{Dask}, & M_{\text{data}} \gtrsim M_{\text{RAM}} \text{ 且批处理} \\ \text{其他引擎}, & \text{低延迟或重度 shuffle} \end{cases}
这是一张选型决策表:数据远小于内存选 Pandas;数据接近或超过内存且是批处理任务选 Dask;需要低延迟或重度 shuffle 时,Dask 不再合适,考虑数据库或 Spark 等专用引擎。
  1. 数据远小于内存Pandas 直接爽快搞定,无需引入调度开销。
    Mdata≪MRAMM_{\text{data}} \ll M_{\text{RAM}}
  2. 数据接近内存且批处理Dask 分块并行表现最佳。
    Mdata≳MRAMM_{\text{data}} \gtrsim M_{\text{RAM}}
  3. 低延迟或重度 shuffleDask 不适合,转投其他引擎。
    低延迟 / 重度 shuffle→其他引擎\text{低延迟 / 重度 shuffle} \rightarrow \text{其他引擎}

AI 应用场景

电商点击流的离线清洗

阶段:数据处理与特征工程

解决的问题:点击日志 30GB,Pandas 一读就内存溢出;传统 shell 切文件 + awk 清洗,代码难维护、规则难复用。Dask 分块读入,用 Pandas 风格代码完成清洗,内存峰值很低。

具体案例:数据工程师用 dd.read_csv 按 64MB 分块读取 30GB 日志,做过滤、去重、列类型转换,再用 persist() 缓存中间结果,最后 groupby 统计 PV/UV 并 compute() 输出 200MB 汇总表。

机器学习特征工程流水线

阶段:特征工程

解决的问题:上亿行用户行为表、几百个特征列,单机 Pandas 在做多表 merge 和滑窗统计时直接卡死。Dask 把特征计算拆成多个分区并行执行,让特征工程从“跑不动”变成“睡得着觉”。

具体案例:机器学习工程师用 Dask 并行做用户维度的 groupby 聚合、时间窗口统计、多表 merge,最终一次 compute() 得到特征矩阵,喂给 XGBoost 训练。

企业内部 Dask 集群统一数据处理

阶段:数据平台建设

解决的问题:各部门离线报表、模型样本生成,单机算力分散且资源浪费。Dask 分布式集群把多台机器的 CPU/内存汇成统一算力池。

具体案例:平台团队搭建 1 个 scheduler + 10 个 worker 的集群,各业务方通过 dask.distributed.Client 提交 DataFrame 任务,资源按队列共享,dashboard 统一监控。

Python 演示

python37 行
import timeimport pandas as pdimport dask.dataframe as dd def main():    # 生成 100 万行模拟订单数据    n = 1_000_000    df = pd.DataFrame({        "user_id": range(n),        "city": ["city_" + str(i % 100) for i in range(n)],        "amount": [i * 0.1 for i in range(n)],    })    print("Pandas 数据大小: {:.1f} MB".format(df.memory_usage(deep=True).sum() / 1e6))     # 对比 1: Pandas 直接分组求和    t0 = time.time()    pandas_out = df.groupby("city")["amount"].mean().sort_index()    t1 = time.time()    print("Pandas 分组耗时: {:.3f}s".format(t1 - t0))     # 对比 2: Dask 分 4 个分区, 惰性构建计算图    ddf = dd.from_pandas(df, npartitions=4)    dask_graph = ddf.groupby("city")["amount"].mean()    print("Dask: 计算图已构建, 尚未真正计算")     # 触发执行    t2 = time.time()    dask_out = dask_graph.compute().sort_index()    t3 = time.time()    print("Dask 分组耗时: {:.3f}s".format(t3 - t2))     # 验证两个引擎结果一致    ok = (pandas_out.values == dask_out.values).all()    print("结果一致:", ok) if __name__ == "__main__":    main()
目的:同一份 100 万行数据,分别用 Pandas 和 Dask 做分组聚合,直观看到“惰性构建 → compute 触发”的流程,并验证两者结果一致。
  1. ddf = dd.from_pandas(df, npartitions=4)把内存中的 Pandas 表切成 4 个分区,这是 Dask 的“拆箱”动作。
  2. dask_graph = ddf.groupby("city")["amount"].mean()只构建计算图,不真正计算,对应“先做计划再行动”。
  3. dask_graph.compute().sort_index()compute() 宣布开工,Dask 沿计算图真正执行。
  4. pandas_out.values == dask_out.values校验两个引擎输出一致,让替换更安心。

常见误区

误区“把 pd 换成 dd,代码不用改,速度就能立刻翻倍,大数据问题彻底解决!”

正解Dask 的核心理念是“装得下”,其次才是“跑得快”。它引入了调度和数据序列化开销,数据量小时往往比 Pandas 更慢。就像搬家公司对大件物品有优势,但搬一盆花纯属杀鸡用牛刀。只有数据超过内存、操作可并行时,多核优势才能显现。

为何常见:各类宣传把“让 Pandas 跑在多核”简化为“无脑加速”,初学者容易产生“替换即快”的错觉。

误区“API 长得一样,那 Dask 就是分布式 Pandas,所有方法直接用都没问题。”

正解Dask 的 API 是 Pandas 的子集,并非全部兼容:索引语义不同(Dask 不维护全局唯一索引)、reset_index 会触发全局计算、iterrows 等行级遍历不支持或极慢。上线前要针对用到的每个方法做兼容性验证,就像搬家前要确认哪些家具能进电梯一样。

为何常见:API 表面相似度太高,让小白误以为“一模一样”。

误区“Dask 惰性计算就是没干活,那我每一步都 compute(),就不怕忘记算了。”

正解频繁 compute() 会反复扫描分区、重复搬运数据,反而更慢。更优做法是:整条流水线最后一次性 compute(),中间结果用 persist() 缓存复用。就像搬家不会每搬一箱就回总部汇报一次,而是攒到最后统一清点。

为何常见:对惰性执行不熟悉,习惯了 Pandas 的“即刻出结果”。

误区“默认是多线程调度,我 apply 一个 Python 函数,几个核一起跑肯定快。”

正解GIL 会让纯 Python 线程互斥,自定义函数塞进线程池基本拿不到加速。正确的做法是改用进程调度器,或者把逻辑改写成 Pandas 内置的向量化操作。装卸工再多,一扇窄门还是只能一个个过。

为何常见:容易忽略 GIL 这个 Python 特有的全局锁限制。

误区“只要用了 Dask,再大的 groupby 都不怕,内存稳稳的。”

正解分布式 groupby 需要 shuffle,中间结果可能大量溢写到磁盘,速度断崖式下跌。要合理设置 npartitions、使用有序 shuffle 或提前聚合;数据规模严重超限时,该考虑更专业的分布式引擎而非硬扛 Dask。

为何常见:对 shuffle 过程的内存放大效应认识不足,只看到“分区”的利好,没看到“重排”的代价。

面试题

Q1. Dask DataFrame 在底层把数据切分成多个什么?

单选题 · easy

  • A.Python 字典
  • B.Pandas DataFrame 分区← 正确答案
  • C.Excel 工作表
  • D.JSON 文件

答案:B。Dask DataFrame 的逻辑结构是一张虚拟大表,物理上按行切成多个 partition,每个 partition 是一个独立的 Pandas DataFrame。就像搬家公司把大木箱拆成小箱子,但每个小箱子依然是“箱子”。

Q2. 下面哪个说法正确描述了 Dask 的惰性计算?

单选题 · easy

  • A.操作立即执行并返回结果
  • B.操作只构建任务图,compute() 才真正计算← 正确答案
  • C.惰性计算只在集群模式生效
  • D.每次调用会自动打印中间结果

答案:B。Dask 的惰性计算指调用 groupby、merge 等方法时只记录计算图,不立即求值;必须调用 compute() 才触发真实计算。这就像搬家前先做计划,真正把箱子搬上车是在“开工”那一刻。

Q3. 哪个选项最适合使用 Dask?

单选题 · mid

  • A.1 万行 CSV 做透视表
  • B.内存放不下的 20GB CSV 做分组聚合← 正确答案
  • C.实时交易系统毫秒级查询
  • D.把 100 个数字求和

答案:B。Dask 的核心价值是处理超出内存的数据。20GB 文件超过可用内存时,Dask 分块读入、并行聚合是理想场景;小数据用 Pandas 更快,毫秒级低延迟查询也并非 Dask 的强项。

Q4. Dask 本地多线程模式,纯 Python UDF 加速受限的主要原因是?

单选题 · mid

  • A.硬盘 IO 太慢
  • B.GIL 限制多线程并行← 正确答案
  • C.网络带宽不足
  • D.内存不够大

答案:B。纯 Python 代码执行时必须持有 GIL(全局解释器锁),导致多线程无法真正并行。这就好比三个装卸工要穿过同一扇窄门,只能一个个过;Pandas 底层 C 函数不长时间持锁,所以多线程才有加速效果。

Q5. Dask 可以运行在哪些环境中?

多选题 · mid

  • A.本地多线程← 正确答案
  • B.本地多进程← 正确答案
  • C.多机分布式集群← 正确答案
  • D.只能运行在单机模式下

答案:ABC。Dask 的调度后端很灵活:本地可用多线程(threads)或多进程(processes),数据量更大时可部署分布式集群,由 scheduler 协调多台机器。D 说“只能单机”显然错误。

Q6. Dask 中控制数据分块个数的核心参数是?

单选题 · mid

  • A.npartitions← 正确答案
  • B.partition_size 属性
  • C.n_workers
  • D.chunk 目录

答案:A。npartitions 是 Dask 中最常用的分区控制参数,例如 dd.from_pandas(df, npartitions=4) 会把表切成 4 个分区。分区数直接影响并行度:太多调度开销大,太少并行不足。

Q7. 关于 Dask 任务图(DAG),错误的说法是?

单选题 · hard

  • A.节点代表计算任务
  • B.边代表任务之间的依赖关系
  • C.DAG 允许存在环← 正确答案
  • D.调度器按依赖关系派发任务

答案:C。DAG 是“有向无环图”,核心约束就是无环。如果任务之间存在环形依赖,调度器将无法确定执行顺序。A、B、D 都正确描述了节点、边和调度逻辑,C 恰恰违背了 DAG 的定义。

Q8. Dask 集群中,scheduler 节点的职责不包括?

单选题 · hard

  • A.管理任务依赖图
  • B.分发任务到 worker
  • C.存储全量数据并逐个计算← 正确答案
  • D.跟踪任务完成状态

答案:C。scheduler 相当于搬家公司总部的调度员,负责画图、派单、收反馈,但数据都存放在各 worker 的内存里,scheduler 不存全量数据、不做批量计算。让调度员亲自搬箱子,反而会阻塞整个协调体系。

Q9. 关于 Dask 和 Pandas 的取舍,正确的有?

多选题 · hard

  • A.数据远小于内存时,Pandas 通常更快← 正确答案
  • B.数据超内存时,Dask 分块处理更合适← 正确答案
  • C.Dask 的任何操作都比 Pandas 快
  • D.Dask 适合把多核或集群资源利用起来← 正确答案

答案:ABD。Dask 有调度和序列化开销,小数据上并不比 Pandas 快,C 说法错误。数据远小于内存时 Pandas 直接操作最简单高效;数据超内存时 Dask 分块并行是正解;Dask 的核心价值正是把多核与多机算力用起来。

Q10. Dask 分布式 groupby-apply 且 apply 为纯 Python 函数,性能调优最合理的手段是?

单选题 · extreme

  • A.线程调度器 + 增加线程数
  • B.进程调度器减少 GIL 影响,并控制分区大小← 正确答案
  • C.把全量数据 collect 回客户端再算
  • D.禁用持久化缓存

答案:B。纯 Python 函数受 GIL 限制,线程调度器加速有限,改为进程调度器(processes)才能绕开 GIL 获得真并行;同时控制分区大小可避免小任务过多造成调度风暴。C 把数据拉回客户端等于放弃分布式优势,D 禁用缓存反而拖慢重复计算。