Skip to main content
 首页 » 编程设计

python之测量外部程序使用的耗时、内存量和 CPU

2025年05月04日17mayingbao

我正在通过 Python 执行外部程序。我想知道调用外部程序的最佳选择是什么,使用 subprocess.Popen() 还是使用 subprocess.call()。此外,我需要测量耗时、外部程序使用的内存量和 CPU。我听说过 psutil,但我真的不知道该选择哪个。

请您参考如下方法:

also I need to measure elapsed time, amount of memory and cpu used by the extern program

(我假设您只需要您平台的 rusage 中可用的信息。而且,由于 Windows 根本没有这样的东西,我也假设您不关心 Windows。如果您需要仅以某些特定于平台的方式提供的附加信息(读取 Linux 的 proc 文件系统,或调用 AIX 的监视器 API,或其他),您几乎不能使用 stdlib 做到这一点,而 psutil 答案是唯一的答案。)

subprocess 库结束调用 fork , 然后是 execv - child 的家庭功能和waitpid - parent 的家庭功能。 (您可以通过从 call 的源开始并从那里向下追踪到其他调用来看到这一点。)

不幸的是,从 child 那里获取资源使用情况的简单方法是调用 wait3 或 wait4 ,而不是 wait 或 waitpid。所以 subprocess 让你疯狂地接近你想要的东西,但还不够。

但是你有几个选择:

  • 如果你只有一个子进程,getrusage(RUSAGE_CHILDREN)就是你所需要的。
  • 您可以启动进程,然后使用 psutil(或特定于平台的代码)从 proc.pid 获取资源信息,然后再获取子进程。
  • 您可以使用 psutil 完成所有操作,而将 subprocess 留在后面。
  • 您可以子类化 subprocess.Popen 以覆盖其 wait 方法。

最后一个比听起来简单得多。如果您查看源代码,只有 3 个地方真正调用了 os.waitpid,并且只有其中一个会影响您的代码;我认为它是 _try_wait 中的那个.所以(未经测试):

class ResourcePopen(subprocess.Popen): 
    def _try_wait(self, wait_flags): 
        """All callers to this function MUST hold self._waitpid_lock.""" 
        try: 
            (pid, sts, res) = _eintr_retry_call(os.wait4, self.pid, wait_flags) 
        except OSError as e: 
            if e.errno != errno.ECHILD: 
                raise 
            # This happens if SIGCLD is set to be ignored or waiting 
            # for child processes has otherwise been disabled for our 
            # process.  This child is dead, we can't get the status. 
            pid = self.pid 
            sts = 0 
        else: 
            self.rusage = res 
        return (pid, sts) 
 
def resource_call(*popenargs, timeout=None, **kwargs): 
    """Run command with arguments.  Wait for command to complete or 
    timeout, then return the returncode attribute and resource usage. 
 
    The arguments are the same as for the Popen constructor.  Example: 
 
    retcode, rusage = call(["ls", "-l"]) 
    """ 
    with ResourcePopen(*popenargs, **kwargs) as p: 
        try: 
            retcode = p.wait(timeout=timeout) 
            return retcode, p.rusage 
        except: 
            p.kill() 
            p.wait() 
            raise 

现在:

retcode, rusage = resource_call(['spam', 'eggs']) 
print('spam used {}s of system time'.format(rusage.ru_stime)) 

将其与 psutil 的混合使用进行比较(在许多平台上以这种方式使用时甚至无法工作……):

p = subprocess.Popen(['spam', 'eggs']) 
ps = psutil.Process(p.pid) 
p.wait() 
print('spam used {}s of system time'.format(ps.cpu_times().system)) 

当然,后者并没有因为没有充分的理由而变得更复杂,它更复杂是因为它更强大、更灵活;你还可以获得rusage不包含的各种数据,你可以在进程运行时每秒获取信息,而不是等到它完成,你可以在Windows上使用它,并且等等……