joblib的Parallel并发计算使用总结及优化

Joblib定义:

joblib是python中提供一系列轻量级管道操作的 工具; 特别在如下3种工具:

  • 函数的透明磁盘缓存和延迟重新计算(记忆模式);
  • 容易且简单的平行计算;
  • 比 pickle更快的 序列化和反序列化 的功能;

 

joblib经过优化,在大数据量时可以更快且强大,并对numpy数组进行特别优化;

 

此文主要 使用 其中的Parallel功能进行并行计算;

 

安装方式:

pip install joblib

 

示例代码如下(可以 从flask抽取出 在脚本中运行Parallel):

# -*- coding: utf-8 -*-
"""
(C) rgc
All rights reserved
create time '2021/01/15 19:45'

Usage:
joblib:在服务启动后 第一次运行时 比后续运行 多耗时0.5s;是因为第一次需要 分配进程的原因
"""
import time

from flask import Flask
from joblib import Parallel, delayed

application = Flask(__name__)


def myfunc(x):
    # [x for x in range(100000000)]
    time.sleep(1)
    return f'finish:{x}'


@application.route('/', methods=['GET'])
def getresult():
    print('开始')
    start = time.time()
    # 创建parallel_obj对象
    parallel_obj = Parallel(n_jobs=-1, verbose=100, backend='loky', timeout=10)
    # 开始调用被并行计算的函数,并给出结果; 实现方式为 调用内置的 __call__方法
    out = parallel_obj(delayed(myfunc)(i) for i in range(2))
    print(f'耗时:{time.time() - start}')
    return str(out)


if __name__ == "__main__":
    application.debug = True
    application.run()

运行结果如下:

 

Parallel类的参数含义及调优如下:

  • n_jobs:进程数
    • -1:表示所有cpu核数都会使用,示例配置8核,假设每核可以运行2个进程,则共16个进程并发;
    • 1:表示不并发,用在debug时; 注意 在用pycharm debug时,此值必须为1,否则报错,且要手动杀掉进程; macos系统flask项目中杀进程命令如下:   lsof -i:端口 | grep Python |cut -b 9-13 | xargs kill -9
    • 其他正整数 表示 并发个数;
  • backend:选择的后台并发类型(建议使用loky)
    • loky: 默认值,第一次程序运行时会创建 n_jobs个loky进程(每次并发时都是这些loky进程进行消费),要多耗费一些时间(1-2s左右),后续不会耗费; 每个loky进程的生命周期在 最后一个请求进来后没有消费的300s后自动被杀死;
    • multiprocessing:多进程,基于进程池(multiprocessing.Pool);在多并发时 如3个请求进来 会产生 3*n_jobs个进程;所有每个请求进来时 都会耗费时间在进程创建和销毁上,这个时间基本为0.2-0.3 s左右; 虽然基于进程池,但是 每个进程的生命周期还是在请求开始到请求;结束之间;
    • threading: 多线程; 不用多说; 因为GIL锁的原因,只能在 IO密集的任务中适用;
  • timeout:设置并发时处理每个任务的超时时间 在 n_jobs !=1时 生效
  • verbose:输出运行时的信息日志
    • 详细级别:如果不为零,则显示进度消息。高于50时,输出将发送到stdout。消息的频率随着详细程度而增加。如果大于10,则报告所有迭代。经过测试并发30个发现 100 耗费1.886s;  1 耗费1.833s;0时耗费1.804s所以在非必要情况下建议 使用 verbose=0

 

最优参数 代码示例如下:

parallel_obj = Parallel(n_jobs=-1, verbose=0, backend='loky', timeout=10)

 

使用时的问题:

问题1:  接口在处理时 会出现 耗时不稳定的情况(10%概率)

原因:  在使用 backend='loky'时,其自动生成loky的worker进程,消费任务;但其机制 在最后一次消费后 如果300s内没有消费为了节省系统资源,会自动杀掉worker进程;然后再 下个请求进来时重建loky进程; 但是这样造成了一个问题, 就是每个loky进程不可能都同时消费任务,造成 不同worker被杀掉的时间不同,在下个请求中 又耗时重建loky进程, 这就造成了 有时 接口耗时 不稳定的情况(10%概率);

解决方案:  为了减少这种概率, 于是修改了Joblib的源码(joblib/_parallel_backends.py文件的 487行),将loky进程的 过期时间改为了 系统运行的近似最大限度(2100000s,210万秒,约24天);   或者可以 删除其过期自动杀掉loky进程的功能,但需要对源码深入分析;

 

问题2: 在使用loky进行多进程并发时,无法 实现  先执行完的任务先返回给 用户端 的功能,让我的接口效应时间大打折扣;

解决方法: 分析源码后,对源码( joblib/parallel.py的918行)进行修改,达到此需求;

 

总结:

  • 解决上面2个问题 修改后的 Joblib 源码地址如下: https://github.com/Rgcsh/joblib/tree/develop
  • 此Parallel在计算密集型 任务中适用,充分利用 CPU每个核的计算资源;且使用loky进程(相当于 生产者-消费者模式)的生命周期较长;比 自己手写的 多进程 更能节省时间和节省资源开销;
  • 此Parallel适合在 单服务器上的 并行计算; 如果是 多服务器的 分布式计算 可以考虑 Parallel Python,但是其在任务分发,网络开销等耗时要比Joblib多 近1s左右;
  • 在服务启动后 第一次运行时 比后续运行 多耗时0.5-1s;是因为第一次需要 生成多个Joky进程; 所以第一次运行的耗时不能算在 平均耗时中的;

 

 

 

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Python是一种具有简洁语法和丰富库的编程语言,常用于数据分析和科学计算。然而,Python的解释器默认是单线程执行的,这在执行复杂计算任务时可能会导致效率问题。为了解决这个问题,可以使用joblib库来实现并行计算Joblib是一个Python库,它基于numpy和Python的multiprocessing模块,提供了一种简单方便的方式来进行并行计算。它的主要功能是将Python函数并行执行,并将计算结果或中间状态保存在内存中。 使用Joblib进行并行计算非常简单,只需将需要并行执行的任务封装成函数,然后使用joblib库提供的Parallel函数来执行。Parallel函数可以指定并行任务的数量,支持多线程和多进程,具体根据计算任务的特点选择。除了使用Parallel函数,还可以使用joblib库提供的其他功能,如内存映射、并行迭代器等。 使用Joblib可以大大提高计算任务的执行效率,尤其是在处理大量数据或复杂计算任务时。它有效地利用了多核处理器的并行计算能力,将计算任务分配给多个线程或进程同时执行,大大缩短了计算时间。 总之,Pythonjoblib库是一种非常有用的工具,可以方便地进行并行计算。它为Python用户提供了简单易用的接口,帮助用户充分发挥多核处理器的并行计算能力,提高程序的执行效率。无论是在科学计算、数据分析还是机器学习领域,使用joblib都能带来很大的便利和效益。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值