$ time foo
real 0m0.003s
user 0m0.000s
sys 0m0.004s
$
Timeの出力において、 'real'、 'user'、および 'sys'とはどういう意味ですか?
私のアプリをベンチマークするときにどれが意味がありますか?
Real、User、およびSysプロセスの時間統計
これらのことの1つは、他のものとは異なります。 Realは、実際の経過時間を指します。ユーザーとシステムは、プロセスのみが使用するCPU時間を参照します。
Realは実時間-コールの開始から終了までの時間です。これは、他のプロセスが使用するタイムスライスやプロセスがブロックされるのに費やした時間(I/Oの完了を待機している場合など)を含むすべての経過時間です。
Userは、ユーザーモードコード(カーネル外)で費やされたCPU時間の量withinプロセス内です。これは、プロセスの実行に使用される実際のCPU時間のみです。他のプロセスと、プロセスがブロックするのに費やした時間は、この数値にはカウントされません。
Sysは、プロセス内のカーネルで費やされたCPU時間です。これは、ユーザー空間で実行されているライブラリコードとは対照的に、カーネル内でのシステムコールに費やされたCPU時間の実行を意味します。 「ユーザー」と同様、これはプロセスが使用するCPU時間のみです。カーネルモード(「スーパーバイザー」モードとも呼ばれる)とシステムコールメカニズムの簡単な説明については、以下を参照してください。
User+Sys
は、プロセスが使用した実際のCPU時間を示します。これはすべてのCPUにまたがるので、プロセスに複数のスレッドがある場合(およびこのプロセスが複数のプロセッサーを搭載したコンピューターで実行されている場合)、Real
(通常発生する) 。出力には、これらの図には、すべての子プロセス(およびその子孫)のUser
およびSys
の時刻が含まれていることに注意してください。 wait(2)
またはwaitpid(2)
を使用しますが、基になるシステムコールはプロセスとその子の統計を個別に返します。
time (1)
によって報告される統計の起源
time
によって報告される統計は、さまざまなシステムコールから収集されます。 「ユーザー」と「システム」は wait (2)
( POSIX )または times (2)
( POSIX ) 、特定のシステムに応じて。 「実」は、 gettimeofday (2)
呼び出しから収集された開始時刻と終了時刻から計算されます。システムのバージョンによっては、time
によって、コンテキストスイッチの数などのさまざまな統計も収集される場合があります。
マルチプロセッサマシンでは、マルチスレッドプロセスまたは子をフォークするプロセスは、異なるスレッドまたはプロセスが並行して実行される可能性があるため、合計CPU時間よりも経過時間が短い場合があります。また、報告される時間統計は異なる起源からのものであるため、元のポスターで示されている例のように、非常に短い実行タスクについて記録された時間は丸め誤差の影響を受ける可能性があります。
カーネルモードとユーザーモードの簡単な入門
Unix、または保護されたメモリオペレーティングシステムでは、 'Kernel'または 'Supervisor' モードは、CPUが動作できる 特権モード を指します。 CPUがこのモードで動作している場合にのみ、セキュリティまたは安定性に影響を与えることができます。これらのアクションは、アプリケーションコードでは使用できません。このようなアクションの例としては、 MMU を操作して別のプロセスのアドレス空間にアクセスすることがあります。通常、 ser-mode コードはこれを行うことができません(正当な理由により)。ただし、カーネルから 共有メモリ を要求できますが、これはcould複数のプロセスによって読み取りまたは書き込みが行われます。この場合、共有メモリは安全なメカニズムを介してカーネルから明示的に要求され、両方のプロセスはそれを使用するために明示的にアタッチする必要があります。
特権モードは、カーネルがこのモードで実行されているCPUによって実行されるため、通常「カーネル」モードと呼ばれます。カーネルモードに切り替えるには、CPUをカーネルモードでの実行に切り替える特定の命令( trap と呼ばれることが多い)を発行する必要がありますそして、ジャンプテーブルに保持されている特定の場所からコードを実行します。セキュリティ上の理由から、カーネルモードに切り替えて任意のコードを実行することはできません-トラップは、 CPUはスーパーバイザーモードで実行されています。明示的なトラップ番号でトラップすると、ジャンプテーブルでアドレスが検索されます。カーネルには有限数の制御されたエントリポイントがあります。
Cライブラリの「システム」呼び出し(特にマニュアルページのセクション2で説明されているもの)には、ユーザーモードコンポーネントがあります。これは、実際にCプログラムから呼び出すものです。背後では、I/Oなどの特定のサービスを実行するためにカーネルに1つ以上のシステムコールを発行する場合がありますが、ユーザーモードで実行されるコードもあります。必要に応じて、ユーザー空間コードからカーネルモードに直接トラップを発行することもできますが、アセンブリ言語のスニペットを作成して、呼び出し用にレジスタを正しく設定する必要がある場合があります。
「sys」の詳細
ユーザーモードではコードで実行できないこと-メモリの割り当てやハードウェア(HDD、ネットワークなど)へのアクセスなど。これらはカーネルの管理下にあり、それだけでそれらを実行できます。 malloc
orfread
/fwrite
などの一部の操作は、これらのカーネル関数を呼び出し、「sys」時間としてカウントします。残念ながら、「mallocへのすべての呼び出しは「sys」時間にカウントされます」ほど単純ではありません。 malloc
の呼び出しは、独自の処理を行い(「ユーザー」時間でカウントされます)、その後、カーネルで関数を呼び出す途中のどこか(「sys」時間でカウントされます)。カーネル呼び出しから戻った後、「ユーザー」にもう少し時間があり、malloc
がコードに戻ります。切り替えがいつ行われ、カーネルモードでどれだけ使用されるかについては、言えません。ライブラリの実装に依存します。また、他の一見無害な関数もバックグラウンドでmalloc
などを使用する場合があります。
受け入れられた答え を拡大するために、私はちょうどreal
≠user
+ sys
であるもう一つの理由を提供したいと思いました。
real
は実際の経過時間を表し、user
およびsys
の値はCPUの実行時間を表します。その結果、マルチコアシステムでは、user
および/またはsys
の時間(およびその合計)は、実際には を超えて リアルタイムになります。たとえば、クラス用に実行しているJavaアプリケーションでは、次の値のセットを取得します。
real 1m47.363s
user 2m41.318s
sys 0m4.013s
• real :プロセスを開始から終了まで実行するのに費やされた実際の時間(あたかもストップウォッチ付きの人間によって測定されたかのように)
• user :計算中にすべてのCPUが費やした累積時間
• sys :メモリ割り当てなど、システム関連のタスク中にすべてのCPUが費やした累積時間。
複数のプロセッサが並行して動作するため、user + sysが実際よりも大きい場合があります。
Realはプロセスの総所要時間を示します。 Userはユーザー定義命令の実行時間を示し、Sysはシステムコールの実行時間を示します。
リアルタイムには待ち時間(I/Oなどの待ち時間)も含まれます。
最小限の実行可能なPOSIX Cの例
物事をより具体的にするために、私はいくつかの最小限のCテストプログラムでのtime
のいくつかの極端なケースを例証したいと思います。
すべてのプログラムは、次のようにコンパイルして実行できます。
gcc -ggdb3 -o main.out -pthread -std=c99 -pedantic-errors -Wall -Wextra main.c
time ./main.out
そしてUbuntu 18.10、GCC 8.2.0、glibc 2.28、Linuxカーネル4.18、ThinkPad P51ラップトップ、Intel Core i7-7820HQ CPU(4コア/ 8スレッド)、2x Samsung M471A2K43BB1-CRC RAMでテスト済み(2×16ギガバイト)。
睡眠
忙しくない睡眠はuser
とsys
のどちらにも数えられず、real
だけに数えられます。
たとえば、1秒間スリープするプログラム
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <unistd.h>
int main(void) {
sleep(1);
return EXIT_SUCCESS;
}
次のように出力されます。
real 0m1.003s
user 0m0.001s
sys 0m0.003s
IOでブロックされたプログラムが利用可能になった場合も同様です。
たとえば、次のプログラムはユーザーが文字を入力してEnterキーを押すのを待ちます。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("%c\n", getchar());
return EXIT_SUCCESS;
}
そしてあなたが1秒の試合をしたい場合は、ちょうど睡眠の例のような出力は以下のようになります。
real 0m1.003s
user 0m0.001s
sys 0m0.003s
マルチスレッド
次の例では、niters
スレッドに対して無駄なCPUの負荷がかかるnthreads
回の繰り返しを行います。
#define _XOPEN_SOURCE 700
#include <assert.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
uint64_t niters;
void* my_thread(void *arg) {
uint64_t *argument, i, result;
argument = (uint64_t *)arg;
result = *argument;
for (i = 0; i < niters; ++i) {
result = (result * result) - (3 * result) + 1;
}
*argument = result;
return NULL;
}
int main(int argc, char **argv) {
size_t nthreads;
pthread_t *threads;
uint64_t rc, i, *thread_args;
/* CLI args. */
if (argc > 1) {
niters = strtoll(argv[1], NULL, 0);
} else {
niters = 1000000000;
}
if (argc > 2) {
nthreads = strtoll(argv[2], NULL, 0);
} else {
nthreads = 1;
}
threads = malloc(nthreads * sizeof(*threads));
thread_args = malloc(nthreads * sizeof(*thread_args));
/* Create all threads */
for (i = 0; i < nthreads; ++i) {
thread_args[i] = i;
rc = pthread_create(
&threads[i],
NULL,
my_thread,
(void*)&thread_args[i]
);
assert(rc == 0);
}
/* Wait for all threads to complete */
for (i = 0; i < nthreads; ++i) {
rc = pthread_join(threads[i], NULL);
assert(rc == 0);
printf("%" PRIu64 " %" PRIu64 "\n", i, thread_args[i]);
}
free(threads);
free(thread_args);
return EXIT_SUCCESS;
}
それから私達は私の8つのハイパースレッドCPUの固定10 ^ 10の反復のための糸の数の関数としてwall、userおよびsysをプロットする:
グラフから、次のことがわかります。
cPUを集中的に使用するシングルコアアプリケーションでは、wallとuserはほぼ同じです。
2コアの場合、ユーザーはウォールの約2倍になります。つまり、ユーザー時間はすべてのスレッドでカウントされます。
ユーザーは基本的に2倍になりましたが、壁は同じままでした。
これは最大8つのスレッドまで続き、これは私のコンピューターの私のハイパースレッドの数と一致します。
8以降、wallは増加し始めます。これは、一定時間内に作業量を増やすための追加のCPUがないためです。
この時点での比率は横ばいです。
sendfile
によるSysの重労働
私が考え出すことができた最も重いsysワークロードは、カーネル空間でファイルコピー操作をするsendfile
を使うことでした: 安全で効率的な方法でファイルをコピーする
そのため、このカーネル内のmemcpy
はCPUに負荷をかける操作になると思いました。
最初に、私は大きな10GiBのランダムファイルを初期化します。
dd if=/dev/urandom of=sendfile.in.tmp bs=1K count=10M
次にコードを実行します。
#define _GNU_SOURCE
#include <assert.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv) {
char *source_path, *dest_path;
int source, dest;
struct stat stat_source;
if (argc > 1) {
source_path = argv[1];
} else {
source_path = "sendfile.in.tmp";
}
if (argc > 2) {
dest_path = argv[2];
} else {
dest_path = "sendfile.out.tmp";
}
source = open(source_path, O_RDONLY);
assert(source != -1);
dest = open(dest_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
assert(dest != -1);
assert(fstat(source, &stat_source) != -1);
assert(sendfile(dest, source, 0, stat_source.st_size) != -1);
assert(close(source) != -1);
assert(close(dest) != -1);
return EXIT_SUCCESS;
}
これは基本的に大体システム時間を予想通りに与える:
real 0m2.175s
user 0m0.001s
sys 0m1.476s
私はtime
が異なるプロセスのシステムコールを区別するかどうかを知りたかったので、私は試してみました:
time ./sendfile.out sendfile.in1.tmp sendfile.out1.tmp &
time ./sendfile.out sendfile.in2.tmp sendfile.out2.tmp &
そして結果は次のとおりです。
real 0m3.651s
user 0m0.000s
sys 0m1.516s
real 0m4.948s
user 0m0.000s
sys 0m1.562s
システム時間は、単一プロセスの場合とほぼ同じですが、プロセスはディスク読み取りアクセスを競合しているため、実時間は長くなります。
したがって、どのプロセスが特定のカーネル作業を開始したのかを実際に考慮しているようです。
Bashのソースコード
Ubuntuでtime <cmd>
を実行するときは、Bashキーワードを使用します。
type time
どの出力:
time is a Shell keyword
そのため、出力文字列のソースコードをBash 4.19のソースコードでgrepします。
git grep '"user\b'
これにより、 execute_cmd.c function time_command
になります。
gettimeofday()
とgetrusage()
times()
これらはすべて Linuxシステムコール および /POSIX関数 です。
GNU Coreutilsのソースコード
我々がそれをと呼ぶなら:
/usr/bin/time
それからGNU Coreutilsの実装を使います。
これはもう少し複雑ですが、関連するソースは resuse.c にあるようです。
wait3
呼び出しtimes
およびgettimeofday