如何在 Linux 上使用 time 命令

您是否好奇程序到底运行了多久?Linux 的 time 命令可以提供详细的时间统计数据,帮助您深入了解程序所使用的系统资源。

时间的多种“身份”

Linux 系统及其衍生出的类 Unix 操作系统有很多不同的版本,每个版本都有自己默认的命令解释器(shell)。目前,最常见的 Linux 默认 shell 是 bash,但也有其他的选择,例如 Z shell (zsh) 和 Korn shell (ksh)。

这些 shell 通常都内置了 time 命令,要么是 内置命令,要么是 保留字。当您在终端中输入 time 时,shell 会执行其内部的 time 命令,而不是使用 Linux 发行版自带的 GNU time 二进制程序。

GNU 版本的 time 命令功能更强大,因为它提供了更多的 选项,使用更加灵活。

到底执行哪个 time 命令?

您可以使用 type 命令来确定系统将执行哪个版本的 time 命令。type 命令会告诉您,shell 是会自行处理您的指令,还是将其传递给 GNU time 二进制程序。

在终端窗口中,输入 type,然后输入空格,再输入 time,然后按 Enter 键。

type time

可以看到,在 bash shell 中,time 是一个保留字。这意味着 bash 默认会使用其内部的 time 命令。

type time

在 Z shell (zsh) 中,time 同样是一个保留字,所以它默认也会使用 shell 的内部 time 命令。

type time

在 Korn shell 中,time 是一个关键字。系统会使用内部 time 命令,而不是 GNU time 程序。

如何运行 GNU time 命令

如果您的 shell 有内置的 time 命令,而您想使用 GNU time 二进制程序,则需要明确指定。 您可以通过以下三种方式来实现:

  • 使用二进制文件的完整路径,例如 /usr/bin/time。您可以使用 which time 命令来查找此路径。
  • 使用 command time 命令。
  • 在 time 命令前加一个反斜杠,例如 \time。

which time 命令返回了二进制文件的路径。

我们可以使用 /usr/bin/time 来启动 GNU time 二进制程序进行测试。它会正常工作,并告知我们没有为其提供任何命令行参数。

键入 command time 同样会生效,我们也会收到相同的用法信息。command 命令会告知 shell 忽略下一个命令,将其交给 shell 之外的程序来执行。

在命令前加一个反斜杠字符,与使用 command 命令的效果相同。

确保您使用的是 GNU time 二进制程序最简单的方法是使用反斜杠选项。

time
\time

第一个 time 命令调用的是 shell 版本的 time,而 \time 调用的是 time 二进制程序。

time 命令的实际应用

现在让我们来计时一些程序的运行时间。 我们将使用两个名为 loop1 和 loop2 的程序。它们分别由 loop1.c 和 loop2.c 文件编译而来。 除了展示低效率编码的影响之外,它们没有其他实际功能。

这是 loop1.c 的代码。它在两个嵌套循环中需要字符串的长度。为了提高效率,字符串长度是在嵌套循环之外预先计算的。

#include "stdio.h"
#include "string.h"
#include "stdlib.h"

int main (int argc, char* argv[])
{
 int i, j, len, count=0;
 char szString[]="how-to-geek-how-to-geek-how-to-geek-how-to-geek-how-to-geek-how-to-geek";

 // get length of string once, outside of loops
 len = strlen( szString );  

 for (j=0; j<1000; j++)
  for (i=0; i<10000; i++)
   count++;

 printf ("%d\n", count);
 return 0;
}

这是 loop2.c 的代码。它在每次外部循环迭代时都会重新计算字符串的长度。这种低效的方式应该会在计时中体现出来。

#include "stdio.h"
#include "string.h"
#include "stdlib.h"

int main (int argc, char* argv[])
{
 int i, j, count=0;
 char szString[]="how-to-geek-how-to-geek-how-to-geek-how-to-geek-how-to-geek-how-to-geek";

 for (j=0; j<1000; j++)
  for (i=0; i<10000; i++)
  count = count + strlen( szString );

 printf ("%d\n", count);
 return 0;
}

现在,让我们运行 loop1 程序,并使用 time 命令来测量其性能。

time ./loop1

接下来,我们对 loop2 程序执行相同的操作。

time ./loop2

现在我们得到了两组输出结果,但它们的格式不太友好。稍后我们可以对其进行改进,但我们先从结果中提取一些信息。

当程序运行时,它会在两种执行模式之间来回切换:用户模式和内核模式。

简单来说,处于用户模式的进程不能直接访问硬件或其自身分配之外的内存。要访问这些资源,进程必须向内核发出请求。如果内核批准请求,则进程会进入内核模式执行,直到请求得到满足。之后,进程会切换回用户模式执行。

loop1 的结果显示,loop1 在用户模式下花费了 0.09 秒。它在内核模式下要么花费了零时间,要么花费的时间太少,四舍五入后被忽略了。总运行时间为 0.1 秒。loop1 在其总运行时间中,平均获得了 89% 的 CPU 时间。

效率较低的 loop2 程序花费了三倍的执行时间。它的总运行时间为 0.3 秒。在用户模式下处理的时间为 0.29 秒。内核模式的时间没有记录。loop2 在其运行期间平均获得了 96% 的 CPU 时间。

格式化 time 命令的输出

您可以使用格式化字符串来自定义 time 命令的输出。格式化字符串可以包含文本和格式说明符。您可以在 time 命令的手册页中找到格式说明符的列表。每个格式说明符代表一条特定的信息。

在打印字符串时,格式说明符会被替换成它们所代表的实际值。例如,CPU 百分比的格式说明符是字母 P。为了指示它是格式说明符,而不是普通的字母,我们需要在其前面加上一个百分号,例如 %P。我们将在下面的示例中使用它。

-f(格式字符串)选项用于指定后面跟的是格式化字符串。

我们的格式化字符串将打印 “Program:” 字符,以及程序的名称(以及您传递给程序的任何命令行参数)。%C 格式说明符代表 “正在计时的命令的名称和命令行参数”。\n 会使输出移动到下一行。

格式说明符有很多,而且区分大小写,所以当您自己执行操作时,请确保输入正确。

接下来,我们将打印 “Total time:”,然后是程序运行的总时间(由 %E 表示)。

我们使用 \n 给出另一个新行。然后我们打印 “User Mode (s)”,然后是在用户模式下花费的 CPU 时间值,由 %U 表示。

我们使用 \n 给出另一个新行。这次我们准备的是内核时间值。我们打印 “Kernel Mode (s)”,然后是在内核模式下花费的 CPU 时间的格式说明符,即 %S。

最后,我们打印 “\nCPU:”,为我们提供一个新行以及该数据值的标题。%P 格式说明符将给出计时进程使用的 CPU 时间的平均百分比。

整个格式化字符串用引号括起来。如果我们要对值的对齐方式进行微调,我们可以包含一些 \t 字符,在输出中添加制表符。

time -f "Program: %C\nTotal time: %E\nUser Mode (s) %U\nKernel Mode (s) %S\nCPU: %P" ./loop1

将 time 命令的输出发送到文件

为了记录您进行的测试的时间数据,您可以将输出发送到文件。要实现此目的,请使用 -o (output) 选项。程序的输出仍然会显示在终端窗口中。只有 time 命令的输出会被重定向到文件。

我们可以重新运行测试,并将输出保存到 test_results.txt 文件中,如下所示:

time -o test_results.txt -f "Program: %C\nTotal time: %E\nUser Mode (s) %U\nKernel Mode (s) %S\nCPU: %P" ./loop1
cat test_results.txt

loop1 程序的输出会显示在终端窗口中,而 time 命令的结果会写入到 test_results.txt 文件中。

如果要将下一组结果附加到同一文件中,则必须使用 -a (append) 选项,如下所示:

time -o test_results.txt -a -f "Program: %C\nTotal time: %E\nUser Mode (s) %U\nKernel Mode (s) %S\nCPU: %P" ./loop2
cat test_results.txt

现在,您应该清楚为什么我们需要在格式化字符串的输出中使用 %C 格式说明符来包含程序名称了。

总结

对于程序员和开发人员来说,time 命令在微调代码时非常有用。它也适用于任何想要深入了解程序运行时发生情况的人。