Dask并行绘图时多进程/线程调度差异及内存限制方法咨询
两种调度方式的核心差异
你遇到的速度差、内存差,本质是两种调度器的运行模型完全不一样,不是官方文档提到的泛泛“并行方式不同”:
- 你之前用的
processes=False配置,说白了就是单进程多线程调度器:所有18个工作线程都跑在启动Python脚本的同一个主进程里,共享主进程的全部内存空间,靠操作系统的线程调度实现并行。这种模式下,因为matplotlib的大部分绘图操作没法完全避开Python全局解释器锁(GIL),18个线程实际是排队抢CPU锁跑计算,名义上18核并行,实际CPU利用率上不去,所以才会比多进程模式慢10倍。你测到这种模式总内存反而更高,也是因为所有线程的临时绘图对象、像素缓冲区都堆在同一个主进程的内存池里,不会在任务结束后立刻把内存还给操作系统,18个线程的临时内存攒在一起,就会出现总内存冲到14G的情况。 - 你更换的
scheduler="processes"配置,是独立多进程调度器:Dask会拉起和num_workers数量完全一致的独立Python工作进程,每个进程有自己独立的内存空间,任务被分发到不同进程里跑,因为不同进程之间不受GIL锁限制,所以能把18个核心的CPU完全跑满,速度提升非常明显。这种模式下你测到总内存只有3G,是因为每个进程的临时对象会在任务结束后随进程的内存回收更及时,不会像单进程多线程那样把所有内存堆在主进程里,所以总内存占用低很多。 - 为啥多进程模式会偶尔内存超限被踢?核心原因是你设置的
memory_limit='1GB'是单个独立工作进程的内存阈值,不是整个任务的总内存上限。这个配置本身存在几个管控盲区:- Dask对worker的内存检测是按固定周期采样的,不是逐字节实时追踪,有时候matplotlib渲染高分辨率PNG时,会在采样间隙临时申请大块的像素内存,一下把单进程内存从几百MB冲到2G以上,因为采样没抓到这个峰值,就不会触发内存回收
- 这个阈值没有把每个worker的固定基础开销算进去:每个独立Python进程跑起来,加载解释器、matplotlib库本身就要占100-200MB的固定内存,再加上主进程本身要存任务队列、ffmpeg拼接的临时缓冲,这些开销都不会被Dask的
memory_limit管控 - 就算Dask检测到某个worker内存超了,触发worker重启的流程也会产生瞬时额外内存:旧worker还没释放完全部内存,新worker已经被拉起来开始加载依赖,这个峰值很容易就碰到超算的内存配额线
多进程调度下可靠限制内存的实操方案
所有方案都不需要牺牲多进程的速度优势,完全贴合Cheyenne超算的配额规则:
- 给Dask自带的内存管控留足冗余,别把阈值设得太松
- 把单worker的
memory_limit从1GB调低到512MB:你测下来正常运行时18个worker总内存才3G,平均每个worker实际只占170MB,512MB的阈值完全能覆盖正常任务的内存需求,就算有临时峰值也不会超。 - 提前把Dask的内存回收策略改成激进模式,别等内存满了才反应:在调用
compute之前加上dask.config.set({"distributed.worker.memory.target": 0.5, "distributed.worker.memory.spill": 0.6, "distributed.worker.memory.terminate": 0.8}),意思是当单个worker用到自身阈值的50%时,就把不常用的临时数据溢写到本地SSD;用到60%时主动触发Python垃圾回收;用到80%时就强制清空worker内存。因为你的任务是完全独立的单帧渲染,没有跨任务的数据依赖,溢写磁盘完全不会丢任务,没有额外副作用。
- 把单worker的
- 从源头上卡总内存的硬阈值,别让所有worker的内存额度加起来超过安全线
- 计算节点跑的话,把所有worker的单进程内存额度加起来,控制在40G以内(留5G的冗余给系统、主进程、ffmpeg的拼接开销,刚好低于45G的节点配额);在登录节点跑的话,把所有worker的单进程额度加起来控制在16G以内(留4G冗余,低于20G的登录节点安全线)。比如18个worker的场景下,单worker设900MB的话,总worker额度就是16.2G,加上冗余刚好卡在安全线内,不会超限。
- 针对matplotlib和xmovie的绘图场景,从任务本身减少临时内存申请
- 每帧渲染完成后,主动清理绘图资源:在单帧绘图的函数最后加
plt.close('all'),手动把引用的像素数组、临时画布对象删掉,再主动触发一次gc.collect(),让worker在当前任务跑完的时候就把内存还给操作系统,不要让临时对象攒在进程内存里。 - 适当降低渲染DPI:临时内存突增的最大来源就是高分辨率单帧的像素数组,只要最终视频的分辨率满足需求,把DPI降10%-20%,单帧的内存占用能降一半,还完全不影响视频清晰度。
- 每帧渲染完成后,主动清理绘图资源:在单帧绘图的函数最后加
- 叠一层系统级的硬内存限制做最后防线
- Linux系统本身可以通过
ulimit给进程设置硬内存上限,不管进程在绘图时临时申请多大内存,只要碰到硬上限就会抛出内存错误,Dask会自动把这个失败的任务派给空闲的worker重跑,不会像之前那样一下把整个节点的内存冲超。比如给每个worker设600MB的硬内存上限,就算matplotlib临时申请超大像素数组,也只会让当前这帧重跑,不会触发系统的配额踢出机制。
- Linux系统本身可以通过
内容的提问来源于stack exchange,提问作者TomCho
相关产品推荐
相关产品推荐

