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

31 天前
 Leon6868

电脑 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

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

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

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

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

我建议你可以用 mmap 共享一段内存,然后流水线处理各个步骤。你可以让 gpt 修改一下测试效果如何。
volvo007
30 天前
主要是核心数就是 8 ,想提速的话用 numpy 或者 polars 改写。初看似乎你这个循环可以完全干掉写成向量计算
deplives
30 天前
AMD Ryzen 7 6800H
一共就 8 核,再多开之后,核间通讯,数据同步,进程的上下文切换,就成了速度的主要原因。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1082262

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX