如何为图像低结构过滤函数delete_tree应用多进程缩短187秒运行时长?
delete_tree 函数多进程优化落地方案
原函数性能瓶颈与改造失败原因
- 原函数耗时核心来自循环逻辑:每遍历一个标签,就需要对全尺寸的
mat_ady做一次全局布尔匹配、计算匹配位置的最大值、符合阈值时二次遍历匹配位置赋值,当标签总数较多时,会产生数十上百次的全矩阵扫描,这是单次运行耗时187秒的核心原因。 - 多进程改造失败的常见诱因:
- 直接通过函数参数把完整高维矩阵传给子进程,跨进程内存拷贝开销远大于实际计算开销,运行速度甚至低于单线程版本
- 未配置共享内存机制,子进程修改的是矩阵的独立副本,主进程无法拿到修改结果
- 任务切分粒度过细,把单个标签作为独立任务派发,进程调度开销完全覆盖并行计算的收益
前置优化:单线程向量化提速(多进程优化的必要前提)
不要直接上多进程,先把循环逻辑替换为一次遍历即可完成全标签最大值计算的向量化实现,这一步可以在不使用并行的前提下把运行时间压缩到原耗时的2%~5%
import time import numpy as np from scipy import ndimage def delete_tree(mat_ady, mat_origin, num_labels, dz_min): tiempo_inicial = time() # 一次遍历计算所有标签对应的像素最大值,替代原循环的多次全图扫描 label_max = ndimage.maximum(mat_origin, labels=mat_ady, index=np.arange(num_labels)) # 筛选出所有满足低结构阈值的标签,一次性定位所有需要清零的像素位置 valid_labels = np.where(label_max < dz_min)[0] mask = np.isin(mat_ady, valid_labels) mat_origin[mask] = 0 tiempo_final = time() print('El tiempo de ejecucion funcion delete_tree:', tiempo_final - tiempo_inicial) return mat_origin
多进程优化实现(零拷贝共享内存模式)
当单线程向量化优化后仍存在性能瓶颈时,再通过多进程并行加速核心计算环节,核心设计是彻底消除跨进程大矩阵拷贝开销:
- 实现逻辑要点
- 使用
multiprocessing.shared_memory将两个输入矩阵挂载为跨进程可直接读写的共享内存块,避免传统多进程模式下的参数序列化拷贝开销 - 按本机可用CPU核心数,将总标签数拆分为大小均等的任务块,每个进程负责处理一个连续标签区间
- 不同进程负责的像素区域完全独立,不存在写冲突,无需额外加锁,最大化并行执行效率
- 使用
- 可落地并行版本代码
import time import numpy as np from scipy import ndimage from multiprocessing import shared_memory, cpu_count, Process def _process_label_block(shm_ady_name, shm_origin_name, mat_shape, mat_dtype, label_start, label_end, dz_min): # 挂载共享内存的矩阵,不触发数据拷贝 existing_shm_ady = shared_memory.SharedMemory(name=shm_ady_name) mat_ady = np.ndarray(mat_shape, dtype=mat_dtype, buffer=existing_shm_ady.buf) existing_shm_origin = shared_memory.SharedMemory(name=shm_origin_name) mat_origin = np.ndarray(mat_shape, dtype=mat_dtype, buffer=existing_shm_origin.buf) # 计算本块负责标签的最大值 block_labels = np.arange(label_start, label_end) label_max = ndimage.maximum(mat_origin, labels=mat_ady, index=block_labels) # 筛选符合低结构阈值的标签 valid_labels = block_labels[label_max < dz_min] # 原地在共享数组上清零对应像素 mask = np.isin(mat_ady, valid_labels) mat_origin[mask] = 0 # 释放共享内存连接 existing_shm_ady.close() existing_shm_origin.close() def delete_tree_parallel(mat_ady, mat_origin, num_labels, dz_min): tiempo_inicial = time() mat_shape = mat_ady.shape mat_dtype = mat_ady.dtype # 创建两个矩阵的共享内存,将原数组数据写入共享内存块 shm_ady = shared_memory.SharedMemory(create=True, size=mat_ady.nbytes) shared_ady = np.ndarray(mat_shape, dtype=mat_dtype, buffer=shm_ady.buf) np.copyto(shared_ady, mat_ady) shm_origin = shared_memory.SharedMemory(create=True, size=mat_origin.nbytes) shared_origin = np.ndarray(mat_shape, dtype=mat_dtype, buffer=shm_origin.buf) np.copyto(shared_origin, mat_origin) # 按CPU核心数拆分标签任务块 n_workers = cpu_count() label_blocks = np.array_split(np.arange(num_labels), n_workers) processes = [] for block in label_blocks: if len(block) == 0: continue p = Process( target=_process_label_block, args=(shm_ady.name, shm_origin.name, mat_shape, mat_dtype, block[0], block[-1]+1, dz_min) ) processes.append(p) p.start() for p in processes: p.join() # 将共享内存的结果拷贝回原数组,清理共享内存资源 np.copyto(mat_origin, shared_origin) shm_ady.close() shm_ady.unlink() shm_origin.close() shm_origin.unlink() tiempo_final = time() print('El tiempo de ejecucion funcion delete_tree (paralelo):', tiempo_final - tiempo_inicial) return mat_origin
预期优化收益
- 单线程向量化优化阶段:原187秒的运行时长可压缩到3~5秒(随矩阵尺寸、标签总数浮动)
- 叠加8核多进程并行优化:运行时长可进一步压缩到1秒以内,计算逻辑与原函数完全一致,无结果偏差
内容的提问来源于stack exchange,提问作者Keffren Guerrero Monroy
相关产品推荐
相关产品推荐

