web-dev-qa-db-ja.com

Time(1)の出力で、 'real'、 'user'、および 'sys'とはどういう意味ですか?

$ time foo
real        0m0.003s
user        0m0.000s
sys         0m0.004s
$

Timeの出力において、 'real'、 'user'、および 'sys'とはどういう意味ですか?

私のアプリをベンチマークするときにどれが意味がありますか?

1485
Iraimbilanja

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などを使用する場合があります。

受け入れられた答え を拡大するために、私はちょうどrealuser + sysであるもう一つの理由を提供したいと思いました。

realは実際の経過時間を表し、userおよびsysの値はCPUの実行時間を表します。その結果、マルチコアシステムでは、userおよび/またはsysの時間(およびその合計)は、実際には を超えて リアルタイムになります。たとえば、クラス用に実行しているJavaアプリケーションでは、次の値のセットを取得します。

real    1m47.363s
user    2m41.318s
sys     0m4.013s
234
lensovet

real :プロセスを開始から終了まで実行するのに費やされた実際の時間(あたかもストップウォッチ付きの人間によって測定されたかのように)

user :計算中にすべてのCPUが費やした累積時間

sys :メモリ割り当てなど、システム関連のタスク中にすべてのCPUが費やした累積時間。

複数のプロセッサが並行して動作するため、user + sysが実際よりも大きい場合があります。

20
varun

Realはプロセスの総所要時間を示します。 Userはユーザー定義命令の実行時間を示し、Sysはシステムコールの実行時間を示します。

リアルタイムには待ち時間(I/Oなどの待ち時間)も含まれます。

14
susenj

最小限の実行可能な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ギガバイト)。

睡眠

忙しくない睡眠はusersysのどちらにも数えられず、realだけに数えられます。

たとえば、1秒間スリープするプログラム

#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    sleep(1);
    return EXIT_SUCCESS;
}

GitHubアップストリーム

次のように出力されます。

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;
}

GitHubアップストリーム

そしてあなたが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;
}

GitHubアップストリーム+プロットコード

それから私達は私の8つのハイパースレッドCPUの固定10 ^ 10の反復のための糸の数の関数としてwall、userおよびsysをプロットする:

enter image description here

グラフから、次のことがわかります。

  • 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;
}

GitHubアップストリーム

これは基本的に大体システム時間を予想通りに与える:

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 にあるようです。

  • 利用可能であれば非POSIX BSDのwait3呼び出し
  • それ以外の場合はtimesおよびgettimeofday