控制子进程的 cpu 使用率

2022-01-19 23:37:07 +08:00
 firejoke

想到一个控制子进程的 cpu 使用率的简单方法。

import platform
import time
from multiprocessing import Process, JoinableQueue

import psutil

from settings import CPU_FREQ, MIN_SLEEP_TIME_OF_SUBPROCESS, testLog
# CPU_FREQ = psutil.cpu_freq().max * pow(10, 9)
# MIN_SLEEP_TIME_OF_SUBPROCESS = 1 / CPU_FREQ


def subp(queue: JoinableQueue):
    while 1:
        if queue.empty():
            _st = 0
        else:
            _st = queue.get()
            queue.task_done()
        time.sleep(_st)


if __name__ == '__main__':
    sysv = platform.system().lower()
    expect = 90
    queue = JoinableQueue()
    queue.put(MIN_SLEEP_TIME_OF_SUBPROCESS)
    sp = Process(target=subp, args=(queue,))
    sp.start()
    spi = psutil.Process(sp.pid)
    if sysv == "windows":
        spi.nice(psutil.HIGH_PRIORITY_CLASS)
    else:
        spi.nice(28)
    if sysv != "windows":
        spi.ionice(psutil.IOPRIO_CLASS_RT, value=7)
    testLog.info(f"pid: {spi.pid}, ppid: {spi.ppid()}")

    time.sleep(5)
    while 1:
        scpu = spi.cpu_percent(MIN_SLEEP_TIME_OF_SUBPROCESS)
        if scpu >= expect:
            testLog.info(f"sub-process cpu: {scpu}")
            if sysv != "windows":
                testLog.info(f"sub-process cpu_num: {spi.cpu_num()}")
            st = 1 / (((scpu - expect) / 100) * CPU_FREQ)
            testLog.info(f"st: {st}")
        else:
            # st = MIN_SLEEP_TIME_OF_SUBPROCESS
            st = 0
        queue.put(st)

原理如下:
if 子进程的 cpu 占用率 > 预期:
    1 / ((当前的占用率- 预期)/ 满载值 * cpu 频率。)
这个值就是子进程需要休眠的时间。
子进程只使用单核时,满载值就是 100 ,子进程再开孙子进程的情况还没考虑。
实测能达到预想效果。
各位还有其他的方案可以借鉴下吗?

3242 次点击
所在节点    Python
14 条回复
wevsty
2022-01-19 23:41:20 +08:00
windows 下面直接设置 job 就可以限制 cpu 使用了。
junnplus
2022-01-19 23:53:01 +08:00
cgroups 不行么
Buges
2022-01-19 23:57:01 +08:00
win 不清楚,Linux 下直接 cgroup
ClericPy
2022-01-20 00:00:19 +08:00
cgroups 基本版本答案一样地在各种环境里存在

虽然我平时用 nice 设置个低优先级就够使了, 但是我把内核占满时间一长会被 kill 不知道咋回事, 所以似乎也需要限制一下 cpu 了(否则就得 sleep 一会了)
firejoke
2022-01-20 09:32:13 +08:00
@wevsty #1
@junnplus #2
@Buges #3
@ClericPy #4
我需要应用的场景,是在资源空闲的时候,用尽预期的所有资源,而在有其他进程也需要占用资源时,能动态的让出资源。
也可以用 cgroups 设定进程组占用 cpu 时间比其他进程组要少,但这样我就得为每一个可能启用的其他任务规划资源占比,并且每次都要手动修改已有的 cgroup 参数。
所以就想着,如果能自动动态的在程序成面规划资源占用的话,就能少动手了😋
wevsty
2022-01-20 09:37:40 +08:00
@firejoke
只是为了系统空闲的时候能使用 CPU 资源,忙的时候不要占用的话,Windows 下面直接设置进程优先级就行了。

设置进程优先级为最低,这样 Windows 会尽可能的调度其他进程,只有空闲的时候才会把 CPU 时间交给低优先级进程。
ClericPy
2022-01-20 10:13:29 +08:00
@firejoke

我也是这个场景啊, 所以我用的 nice 把优先级调低凑合用着, 实际效果没发现多好, 想做自动伸缩资源占用的场景很多, 比如 Hadoop 上想用空闲资源计算不重要的离线, 那里提供的优先级队列在队列已经启动的时候依然没法让出 CPU 来...

同关注一下看看有没有更好方案
firejoke
2022-01-20 10:38:14 +08:00
@wevsty #6
在系统成面设置进程优先级来控制资源调度确实要方便很多,但就像 @ClericPy #7 描述的,操作系统内的资源调度对我们来说约等于是黑盒,不能达到想要的效果,所以如果能在程序成面做到控制的话,可控度就高多了,希望能找到更合适的方案。
2i2Re2PLMaDnghL
2022-01-20 11:02:02 +08:00
@ClericPy dmesg 看下 kill 的原因? nice 值好像会影响到 OOM kill 的顺序。
(不如用竞价实例(误
wevsty
2022-01-20 11:33:47 +08:00
@firejoke
常见的系统内核都被人研究的比较透彻了,所以这些内核提供的资源调度方式都是比较好预期的。

以调度的眼光来看,一个耗时任务所需要的 CPU 时间是无穷大的(因为无法预知具体需要多少时间来完成任务),而 CPU 时间显然是有限的。
对 Windows 来说,Windows 会优先保证在调度周期内每个线程都能得到一定时间的执行的机会,在此基础上,剩余的时间按照优先级分配给各个线程。也就是说,进程优先级实际上保证的也是任务在系统中所占用的时间比例。
我不知道所谓低优先级任务启动时不能让出 CPU 时间是一个什么概念,但是我十分确定,在高优先级线程需要 CPU 时间时,对比低优先级线程高优先级线程会获得更多的 CPU 时间。
在 Windows 中,还可以给进程设置 JOBOBJECT_CPU_RATE_CONTROL_INFORMATION ,可以设定 JOB 中进程使用的 CPU 占比或者时间,可以设定为动态比值,也可以设定绝对周期。这样可以比较好的控制 CPU 时间的分配。

最后资源调度本来就是内核的活,用户态根本拿不到准确的数据来进行调度。
如果内核提供的优先级+CPU 时间限制还满足不了你的需求,那你可能需要的是自己定制一个系统内核。
ClericPy
2022-01-20 12:42:54 +08:00
@2i2Re2PLMaDnghL
问题就是不是 OOM Kill 的, python 里多进程里每个进程高并发协程跑满 CPU, 内存只用了 200 多 MB, 任务主要就是流式下载图片并流式上传到 S3... 连个 SIGABRT 都没发, 看 journal 也没看出啥东西

过段时间看看整 Serverless 上了, 竞价实例早就想用了, 但是运维跑路了申请不下来
firejoke
2022-01-20 17:41:36 +08:00
@wevsty #10 我晚上回去试试,不过 psutil 库只能设置指定进程的优先级,在 Windows 是用 SetPriorityClass 实现的,只有几个固定优先级和后台模式。
firejoke
2022-01-20 20:25:32 +08:00
@wevsty #10 在 Windows 上测了。

测试环境:
测试中把所有进程包括子进程都调整到同一 cpu 核上;
运行中通过任务管理器确认了程序内设置的优先级是生效的;
子进程用 multiprocessing.Process 生成的,在 Windows 上是 spawn 模式,会启动一个全新的解释器运行子进程。

测试结果:
两个独立进程之间,可以通过设置优先级,让优先级高的占用更多 cpu 时间,而相同优先级的会竞争。
同一父进程的两个子进程之间,优先级没起到作用;
不同父进程的两个子进程之间,不论是设置父进程的优先级还是子进程的优先级或者全都配置,两个子进程仍然会竞争。
woodpenker
2022-01-24 21:55:23 +08:00
kill STOP/CONT + sleep 就搞定了

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

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

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

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

© 2021 V2EX