Python subprocess与/usr/bin/time:如何捕获时间信息,但忽略所有其他输出?

4
我正试图通过子进程调用来测量可执行程序的执行时间。我不想发出可执行程序的输出(无论是stderr还是stdout)。
我已经尝试过timeit和resource库,但两者都没有准确地捕获进程的时间,似乎它只捕捉Python工作线程中的时间。
下面的尝试将会由于stderr重定向而丢失时间信息。然而,如果没有stderr重定向,'f_cmd'命令的stderr输出将被发出。
def doWithTiming(f_cmd):
    DEVNULL = open(os.devnull, 'w')
    return subprocess.check_output([ "/usr/bin/time", "--format=%e seconds"] + f_cmd.split(), stderr=DEVNULL)

我该如何忽略f_cmd的所有输出,但保留/usr/bin/time的输出?

2个回答

9

%e /usr/bin/time格式为:

进程使用的实际流逝(墙钟)时间,以秒为单位。

要运行一个抑制stdout/stderr并获取经过的时间的子进程:

#!/usr/bin/env python
import os
import time
from subprocess import check_call, STDOUT

DEVNULL = open(os.devnull, 'wb', 0)

start = time.time()
check_call(['sleep', '1'], stdout=DEVNULL, stderr=STDOUT)
print("{:.3f} seconds".format(time.time() - start))

timeit.default_timer在Python 2的POSIX上等同于time.time,因此,除非您使用timeit的方式不正确,否则应该可以获得有效的时间。


resource模块返回的信息不包括"real"时间,但您可以使用它来获取"user"和"sys"时间,即进程在用户模式下花费的"CPU秒数"和在内核模式下花费的"CPU秒数",分别如下所示:

#!/usr/bin/env python
import os
import time
from subprocess import Popen, STDOUT

DEVNULL = open(os.devnull, 'wb', 0)

start = time.time()
p = Popen(['sleep', '1'], stdout=DEVNULL, stderr=STDOUT)
ru = os.wait4(p.pid, 0)[2]
elapsed = time.time() - start
print(" {:.3f}real {:.3f}user {:.3f}system".format(
       elapsed, ru.ru_utime, ru.ru_stime))

您可以使用psutil.Popen启动子进程,并以可移植的方式获取在子进程运行时的其他信息(CPU、内存、网络连接、线程、文件描述符、子进程等)。

另请参见:如何使用Python中的psutil获取程序的最大内存使用情况


为了进行测试(确保基于time.time()的解决方案产生相同的结果),您可以捕获/usr/bin/time的输出:

#!/usr/bin/env python
import os
from collections import deque
from subprocess import Popen, PIPE

DEVNULL = open(os.devnull, 'wb', 0)

time_lines_count = 1 # how many lines /usr/bin/time produces
p = Popen(['/usr/bin/time', '--format=%e seconds'] + 
          ['sleep', '1'], stdout=DEVNULL, stderr=PIPE)
with p.stderr:
    q = deque(iter(p.stderr.readline, b''), maxlen=time_lines_count)
rc = p.wait()
print(b''.join(q).decode().strip())

或者使用带有命名管道的-o选项:

#!/usr/bin/env python
import os
from contextlib import contextmanager
from shutil     import rmtree
from subprocess import Popen, STDOUT
from tempfile   import mkdtemp

DEVNULL = open(os.devnull, 'wb', 0)

@contextmanager
def named_pipe():
    dirname = mkdtemp()
    try:
        path = os.path.join(dirname, 'named_pipe')
        os.mkfifo(path)
        yield path
    finally:
        rmtree(dirname)

with named_pipe() as path:
    p = Popen(['/usr/bin/time', '--format=%e seconds', '-o', path] + 
              ['sleep', '1'], stdout=DEVNULL, stderr=STDOUT)
    with open(path) as file:
        time_output = file.read().strip()
    rc = p.wait()
print(time_output)

0

你的问题不是与Python有关,而是与Linux时间实用程序的行为有关。时间将在进程写入任何stderr消息后写入stderr。您会在shell中运行它时获得此效果。子进程将完全复制shell命令的行为。

我建议您将stderr重定向到suprocess.PIPE,然后解析它。这应该不难。

或者,您可以使用-time和-o选项将时间信息写入输出文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接