首页 > 解决方案 > 为什么 psutil CPU 使用率太低?

问题描述

我用 Python 编写了一个性能监视器函数,psutil用于测量 CPU、GPU 和磁盘 I/O,以对另一个应用程序进行基准测试。它在我最初的测试中运行良好,但我发现它在接近 100% 时显着低估了 CPU 使用率。我正在寻求帮助,弄清楚为什么会发生这种情况。

这个功能可能看起来过于复杂,但我正在尝试做两件事:实时显示当前使用情况;并在最后报告最高的 5 秒滚动平均值。这是因为我无法预测子流程需要多长时间,并且我想消除短期波动。

该函数的简化版本如下。在此示例中,我删除了所有 GPU 和磁盘测量,并稍微更改了打印语句以进行调试。尽管我尝试每秒采样一次,但elapsed有时当 CPU 使用率非常高时,时间波动高达 1.8。

这是在 Windows 10 上运行的。任务管理器显示恒定的 100% CPU 使用率,没有波动,如果我psutil.cpu_times_percent()在另一个也显示 100% 的窗口中运行,而我的功能显示大约 90%。

import psutil
import time
import shlex
import subprocess
from concurrent.futures import ThreadPoolExecutor


def perf_monitor(proc):
    # measure CPU, GPU, and Disk R/W every 1 seconds, and calculate a rolling average every 5 seconds
    # return the maximum of each rolling average
    rolling_interval = 5
    sample_interval = 1
    rolling_samples = []
    stats = {
        'cpu': 0,
    }
    cpu_count = psutil.cpu_count()

    while True: # loop until subprocess is finished
        # reset starting values
        start = time.time()
        cpu1 = psutil.cpu_times()
        cpu1 = cpu1.user + cpu1.system

        # measure again after the interval
        time.sleep(sample_interval)
        cpu2 = psutil.cpu_times()
        cpu2 = cpu2.user + cpu2.system
        
        # list starts at zero and counts up, then remains at 5
        elapsed = time.time() - start # may be slightly longer than sample_interval
        rolling_samples.append({
            'cpu': (cpu2 - cpu1) / cpu_count / elapsed,
        })
        # skip reporting for the first 5 seconds warm up
        if len(rolling_samples) < rolling_interval:
            continue
        
        # get the rolling average over a defined interval (5 seconds)
        rolling_avg_cpu = sum([sample['cpu'] for sample in rolling_samples]) / rolling_interval

        print([sample['cpu'] for sample in rolling_samples], elapsed) # for debugging
        print(f"CPU: {rolling_avg_cpu:.1%}  ", end='\n',)

        # update each stat only if it has increased
        if rolling_avg_cpu > stats['cpu']:
            stats['cpu'] = rolling_avg_cpu
        
        # remove oldest sample so we always keep 5
        del rolling_samples[0]

        # return stats when render is finished
        if proc.poll() is not None:
            return stats

if __name__ == "__main__":
    command = '"MyApp.exe'
    with ThreadPoolExecutor() as executor:
        proc = subprocess.Popen(shlex.split(command))
        thread = executor.submit(perf_monitor, proc)
        stats = thread.result()
    
    print(stats)

标签: pythonperformancepsutil

解决方案


推荐阅读