V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
Leon6868
V2EX  ›  Python

Python joblib 在 n_jobs 大于 8 后几乎没有性能增益

  •  
  •   Leon6868 · 65 天前 · 1592 次点击
    这是一个创建于 65 天前的主题,其中的信息可能已经有所发展或是发生改变。

    电脑 CPU 为 AMD Ryzen 7 6800H ,8 核 16 进程

    系统为 Windows 11

    任务为对一组数据做分段 FFT ,因为每段 FFT 相互无关,所以将整段数据分为 n_jobs 块后每块并行计算,试图加速(具体代码不能公开,正在整理一份能复现的代码)。但是发现了奇怪的情况,保持其他参数不变,使用 parallel = Parallel(n_jobs=int(n_jobs)) ,当 n_jobs 大于 4 后,总体用时不会下降。

    代码性能测试

    深入进程测试后发现每个进程内部的 for 循环内的代码拖慢了速度,代码如下(正常来说怎么测试每行代码的性能呢……望大佬指路!):

    tCost = []
    
    for i in batchTask:
        tCost.append([time.time()])
    
        startTime = sampleDot[0] + i * step
        endTime = startTime + step
    
        splitSampleDot = sampleDot[
            np.where((sampleDot >= startTime) & (sampleDot < endTime))
        ]
    
        tCost[-1].append(time.time() - tCost[-1][0]) # ckpt t0
    
        splitData = np.array(list(zip(splitSampleDot, linearData(splitSampleDot))))
    
        tCost[-1].append(time.time() - tCost[-1][0]) # ckpt t1
        
        signal, powerDensity = getFftResult(
            splitData,
            splitSampleDot,
            float(sampleRate),
            0.0,
            0.8,
            float(minFreq),
        )
    
        tCost[-1].append(time.time() - tCost[-1][0]) # ckpt t2
    
        powerDensity[powerDensity < displayThreshold] = np.nan
    
        tCost[-1].append(time.time() - tCost[-1][0]) # ckpt t3
    
        fftDataList.append(powerDensity)
        fftFreqList.append(signal)
    
        fftStartTimeList.append(datetime.fromtimestamp(startTime))
    
        tCost[-1].append(time.time() - tCost[-1][0]) # ckpt t4
    
        realTimeDateObjList.append(
            mdates.date2num(np.vectorize(datetime.fromtimestamp)(splitSampleDot))
        )
    
        tCost[-1].append(time.time() - tCost[-1][0]) # ckpt t5
    

    请问为什么会出现这种情况呢?有哪些办法能进一步提升性能呢?

    18 条回复    2024-10-22 12:31:15 +08:00
    BingoXuan
        1
    BingoXuan  
       65 天前   ❤️ 1
    因为你只有 8 核
    Leon6868
        2
    Leon6868  
    OP
       65 天前
    @BingoXuan #1 请问为什么用上 8 核后相比 1 线程也只有 50% 的性能提升呢?
    vicalloy
        3
    vicalloy  
       65 天前
    看一下每个核的 CPU 占用率,如果负载满了就是到性能瓶颈了。
    BingoXuan
        4
    BingoXuan  
       65 天前
    t2 才是你真正计算部分吧,其他都是处理数据。感觉就是处理数据拖后腿了。
    Donaldo
        5
    Donaldo  
       65 天前
    @Leon6868 #2 上下文切换,缓存同步,核间通信也有开销的,实际上很难(甚至可以说不可能)做到 N 个核的效率是 1 个核的 N 倍。
    Leon6868
        6
    Leon6868  
    OP
       65 天前
    @BingoXuan #4 是的, `n_jobs` 小于 8 时 `t2` 几乎没影响,但是为什么这些数据处理代码会随着 `n_jobs` 增大而线性变慢呢,不同进程之间理论上应该不会相互影响?
    vicalloy
        7
    vicalloy  
       65 天前
    进程多变慢是正常的,线程切换是有代价的。
    NoOneNoBody
        8
    NoOneNoBody  
       65 天前   ❤️ 1
    windows
    python 多进程还有很多消耗,基本上达不到 total/n 这么完美的效果
    然后,你需要一些特殊的包,控制 CPU 亲和度,把闲置的 CPU 核分给进程
    另外,我的经验是外部跑一些消耗的软件,如播放器、浏览器,python 多进程的效率会大幅降低,只有保留 CPU 专用,才能保持一个相对较高效率
    还有内存,当内存用满,也是会大幅效率降低

    如果数据不是十份庞大,多进程提升不大,数据庞大且内存足够,建议想办法跑 numba ,如果实在难以跑 numba ,也要尽量用 np/pd 的向量函数

    你这里用了大量 append ,考虑一下换成一次生成的思路
    或者改写方式,就是预置长度,所有元素为默认值,然后定位再赋值计算结果
    yzongyue
        9
    yzongyue  
       65 天前
    瓶颈不一定在 cpu , 跑代码的时候,把任务管理器打开, 看看是不是内存/磁盘 io 满了
    Leon6868
        10
    Leon6868  
    OP
       65 天前
    @yzongyue #9 CPU 满的, 内存和 io 没满,数据是全部加载到内存中然后再处理的
    Riyue
        11
    Riyue  
       65 天前
    numpy 底层的 mkl openblas 自动利用多核,不知道会不会是原因之一
    Clouder1
        12
    Clouder1  
       65 天前   ❤️ 1
    一个原因是你只有 8cores ,另一部分原因是:多进程本身就有开销,比如启动进程的开销、进程通信拷贝数据的开销,以及如果你使用的某些库本身就会使用多线程、向量化之类的手段优化性能,开太多进程其实对并行计算也没有多少帮助。提名 numpy/polars 等。
    如果想要逐行 profile ,可以参考 https://stackoverflow.com/questions/3927628/how-can-i-profile-python-code-line-by-line
    timethinker
        13
    timethinker  
       65 天前   ❤️ 1
    详见:阿姆达尔定律
    JacHammer
        14
    JacHammer  
       65 天前
    更像是 CPU 撞功耗/温度墙了。在占用 CPU 核心数不多时,每个核都会以较高频率和功率运行;等每个核心逐渐被占用时,所有的核心也会逐渐降低频率和功率,自然此时的单核性能会下降;如果在笔记本电脑这种对功耗/温度限制大的设备上运行则尤其如此。
    当然还有楼上提到的各种非硬件开销等等,但我并不认为非硬件原因为主要因素。你可以试试在桌面或者服务器 CPU 等没有太大功率与温度限制的环境下进行相同测试,总用时统计曲线应该会更加线性。
    hertzry
        15
    hertzry  
       65 天前 via iPhone
    试试 Multiprocessing 吧。
    BingoXuan
        16
    BingoXuan  
       65 天前
    @Leon6868
    我在怀疑是缓存击中率太低了。处理数据过程需要不断交换内存数据。进程越多,每个进程需要读写数据页面过于分散,导致缓存不断刷新。单进程和 fft 处理不存在这个问题。

    我建议你可以用 mmap 共享一段内存,然后流水线处理各个步骤。你可以让 gpt 修改一下测试效果如何。
    volvo007
        17
    volvo007  
       64 天前 via iPhone
    主要是核心数就是 8 ,想提速的话用 numpy 或者 polars 改写。初看似乎你这个循环可以完全干掉写成向量计算
    deplives
        18
    deplives  
       64 天前
    AMD Ryzen 7 6800H
    一共就 8 核,再多开之后,核间通讯,数据同步,进程的上下文切换,就成了速度的主要原因。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   991 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 21:22 · PVG 05:22 · LAX 13:22 · JFK 16:22
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.