28

Linuxでコンテキストスイッチに費やされた時間を見つけるためにacプログラムを書くことはできますか?コードをお持ちの場合は共有していただけますか?ありがとう

4

6 に答える 6

25

切り替え時間のプロファイリングは非常に困難ですが、カーネル内のレイテンシープロファイリングツールとoprofile(カーネル自体のプロファイリングが可能)が役立ちます。

インタラクティブなアプリケーションのパフォーマンスをベンチマークするために、予期しないレイテンシスパイクを測定するlatencybenchと呼ばれる小さなツールを作成しました。

// Compile with g++ latencybench.cc -o latencybench -lboost_thread-mt
// Should also work on MSVC and other platforms supported by Boost.

#include <boost/format.hpp>
#include <boost/thread/thread.hpp>
#include <boost/date_time.hpp>
#include <algorithm>
#include <cstdlib>
#include <csignal>

volatile bool m_quit = false;

extern "C" void sighandler(int) {
    m_quit = true;
}

std::string num(unsigned val) {
    if (val == 1) return "one occurrence";
    return boost::lexical_cast<std::string>(val) + " occurrences";
}

int main(int argc, char** argv) {
    using namespace boost::posix_time;
    std::signal(SIGINT, sighandler);
    std::signal(SIGTERM, sighandler);
    time_duration duration = milliseconds(10);
    if (argc > 1) {
        try {
            if (argc != 2) throw 1;
            unsigned ms = boost::lexical_cast<unsigned>(argv[1]);
            if (ms > 1000) throw 2;
            duration = milliseconds(ms);
        } catch (...) {
            std::cerr << "Usage: " << argv[0] << " milliseconds" << std::endl;
            return EXIT_FAILURE;
        }
    }
    typedef std::map<long, unsigned> Durations;
    Durations durations;
    unsigned samples = 0, wrongsamples = 0;
    unsigned max = 0;
    long last = -1;
    std::cout << "Measuring actual sleep delays when requesting " << duration.total_milliseconds() << " ms: (Ctrl+C when done)" << std::endl;
    ptime begin = boost::get_system_time();
    while (!m_quit) {
        ptime start = boost::get_system_time();
        boost::this_thread::sleep(start + duration);
        long actual = (boost::get_system_time() - start).total_milliseconds();
        ++samples;
        unsigned num = ++durations[actual];
        if (actual != last) {
            std::cout << "\r  " << actual << " ms " << std::flush;
            last = actual;
        }
        if (actual != duration.total_milliseconds()) {
            ++wrongsamples;
            if (num > max) max = num;
            std::cout << "spike at " << start - begin << std::endl;
            last = -1;
        }
    }
    if (samples == 0) return 0;
    std::cout << "\rTotal measurement duration:  " << boost::get_system_time() - begin << "\n";
    std::cout << "Number of samples collected: " << samples << "\n";
    std::cout << "Incorrect delay count:       " << wrongsamples << boost::format(" (%.2f %%)") % (100.0 * wrongsamples / samples) << "\n\n";
    std::cout << "Histogram of actual delays:\n\n";
    unsigned correctsamples = samples - wrongsamples;
    const unsigned line = 60;
    double scale = 1.0;
    char ch = '+';
    if (max > line) {
        scale = double(line) / max;
        ch = '*';
    }
    double correctscale = 1.0;
    if (correctsamples > line) correctscale = double(line) / correctsamples;
    for (Durations::const_iterator it = durations.begin(); it != durations.end(); ++it) {
        std::string bar;
        if (it->first == duration.total_milliseconds()) bar = std::string(correctscale * it->second, '>');
        else bar = std::string(scale * it->second, ch);
        std::cout << boost::format("%5d ms | %s %d") % it->first % bar % it->second << std::endl;
    }
    std::cout << "\n";
    std::string indent(30, ' ');
    std::cout << indent << "+-- Legend ----------------------------------\n";
    std::cout << indent << "|  >  " << num(1.0 / correctscale) << " (of " << duration.total_milliseconds() << " ms delay)\n";
    if (wrongsamples > 0) std::cout << indent << "|  " << ch << "  " << num(1.0 / scale) << " (of any other delay)\n";
}

Ubuntu2.6.32-14-汎用カーネルでの結果。測定中、私は4つのコアでC ++コードをコンパイルし、同時にOpenGLグラフィックスでゲームをプレイしていました(より面白くするため):

Total measurement duration:  00:01:45.191465
Number of samples collected: 10383
Incorrect delay count:       196 (1.89 %)

Histogram of actual delays:

   10 ms | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 10187
   11 ms | *************************************************** 70
   12 ms | ************************************************************ 82
   13 ms | ********* 13
   14 ms | ********* 13
   15 ms | ** 4
   17 ms | *** 5
   18 ms | * 2
   19 ms | **** 6
   20 ms |  1

                              +-- Legend ----------------------------------
                              |  >  169 occurrences (of 10 ms delay)
                              |  *  one occurrence (of any other delay)

rtパッチを適用したカーネルを使用すると、10〜12ミリ秒だけで、はるかに優れた結果が得られます。

印刷出力の凡例に丸め誤差などが発生しているようです(貼り付けられたソースコードはまったく同じバージョンではありません)。私はリリースのためにこのアプリケーションを本当に磨いたことはありません...

于 2010-03-03T02:41:23.567 に答える
8

スーパーユーザー権限を持っている場合は、コンテキスト スイッチのプローブ ポイントを使用して SystemTap プログラムを実行し、それぞれの現在時刻を出力できます。

probe scheduler.ctxswitch {
    printf("Switch from %d to %d at %d\n", prev_pid, next_pid, gettimeofday_us())
}

出力データがどれほど信頼できるかはわかりませんが、数値を取得するための迅速かつ簡単な方法です。

于 2010-03-03T03:06:57.373 に答える
6

短い答え - いいえ。以下の長い答え。

コンテキストの切り替えは、大まかに次のいずれかの場合に発生します。

  1. ユーザー プロセスがシステム コールまたはトラップ (ページ フォールトなど) を介してカーネルに入り、要求されたデータ (ファイル コンテンツなど) がまだ利用できないため、カーネルはユーザー プロセスをスリープ状態にし、別の実行可能なプロセスに切り替えます。
  2. カーネルは、指定されたユーザー プロセスがそのフルタイム クォンタムを消費したことを検出します (これは、タイマー割り込みから呼び出されたコードで発生します)。
  3. データは、現在スリープ状態にある優先度の高いプロセスで使用できるようになります (これは、IO 割り込みから、またはその周辺で呼び出されたコードから発生します)。

スイッチ自体は一方向であるため、ユーザーランドでできる最善のことは (それがあなたが求めていることだと思います)、プロセスから別のプロセスへの RTT のようなものを測定することです。他のプロセスも、その作業を行うのに時間がかかります。もちろん、これに関して 2 つ以上のプロセスを連携させることもできますが、問題は、カーネルはプロセスの 1 つが次に選択されることを保証しないということです。RTスケジューラを使用して特定のプロセスに切り替えることはおそらく可能ですが、ここではアドバイスはありません.提案を歓迎します.

于 2010-03-03T03:03:41.260 に答える
6

コンテキストの切り替えを秒単位、ミリ秒単位、さらにはマイクロ秒単位で測定するとどう思いますか。すべてナノ秒未満で起こっています。測定可能なコンテキスト切り替えに膨大な時間を費やしたい場合は、 ... Assembly で記述されたリアルモードのカーネル タイプ コードを試してみると、何かがわかるかもしれません。

于 2011-11-19T05:46:37.983 に答える
3

コンテキスト スイッチのコストを測定するのは少し難しいです。単一の CPU で 2 つのプロセスを実行し、それらの間に 3 つの Linux パイプを設定することで、コンテキスト スイッチに費やされた時間を計算できます。

  • プロセス間で文字列を共有するための 2 つのパイプ
  • 3 つ目は、子プロセスで費やされた時間を共有するために使用されます。

次に、最初のプロセスが最初のパイプへの書き込みを発行し、2 番目のパイプでの読み取りを待ちます。最初のプロセスが 2 番目のパイプから何かを読み取るのを待っているのを確認すると、OS は最初のプロセスをブロック状態にし、最初のパイプから読み取ってから 2 番目のパイプに書き込む別のプロセスに切り替えます。2 番目のプロセスが最初のパイプから再度読み取ろうとすると、ブロックされ、通信の往復サイクルが継続します。このような通信のコストを繰り返し測定することで、コンテキスト スイッチのコストを適切に見積もることができます。

複数の CPU を持つシステムでは、コンテキスト スイッチのコストを測定する際に 1 つの問題が発生します。このようなシステムで必要なことは、コンテキスト切り替えプロセスが同じプロセッサ上にあることを確認することです。幸いなことに、ほとんどのオペレーティング システムには、プロセスを特定のプロセッサにバインドするための呼び出しがあります。たとえば、Linux では、sched_setaffinity() 呼び出しが探しているものです。両方のプロセスが同じプロセッサ上にあることを確認することで、同じ CPU で 1 つのプロセスを停止して別のプロセスを復元する OS のコストを確実に測定できます。

ここでは、プロセス間のコンテキスト スイッチを計算するためのソリューションを投稿しています。

    #define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sched.h>
#include <stdlib.h>
#include <string.h>
#include <linux/unistd.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>

pid_t getpid( void )
{
    return syscall( __NR_getpid );
}

int main()
{
    /*********************************************************************************************
        To make sure context-switching processes are located on the same processor :
        1. Bind a process to a particular processor using sched_setaffinity.    
        2. To get the maximum priority value (sched_get_priority_max) that can be used with 
           the scheduling algorithm identified by policy (SCHED_FIFO).** 
        **********************************************************************************************/

    cpu_set_t set;
    struct sched_param prio_param;
    int prio_max;

    CPU_ZERO( &set );
    CPU_SET( 0, &set );
        memset(&prio_param,0,sizeof(struct sched_param));

    if (sched_setaffinity( getpid(), sizeof( cpu_set_t ), &set ))
    {
        perror( "sched_setaffinity" );
                exit(EXIT_FAILURE);
    }

    if( (prio_max = sched_get_priority_max(SCHED_FIFO)) < 0 )
    {
                perror("sched_get_priority_max");
        }

    prio_param.sched_priority = prio_max;
    if( sched_setscheduler(getpid(),SCHED_FIFO,&prio_param) < 0 )
    {
                perror("sched_setscheduler");
                exit(EXIT_FAILURE);
        }

    /*****************************************************************************************************
        1. To create a pipe for a fork, the parent and child processes use pipe to read and write, 
           read and write string, using this for context switch.
        2. The parent process first to get the current timestamp (gettimeofday), then write to the pipe,. 
           Then the child should be read in from the back, 
           then the child process to write string, the parent process reads. 
           After the child process to get the current timestamp. 
           This is roughly the difference between two timestamps n * 2 times the context switch time.
    *******************************************************************************************************/

    int     ret=-1;
    int     firstpipe[2];
    int     secondpipe[2];
    int     timepipe[2];
        int     nbytes;
        char    string[] = "Hello, world!\n";
        char    temp[] = "Sumit Gemini!\n";
        char    readbuffer[80];
        char    tempbuffer[80];
    int     i=0;
    struct  timeval start,end;

    // Create an unnamed first pipe
        if (pipe(firstpipe) == -1) 
    {
            fprintf(stderr, "parent: Failed to create pipe\n");
            return -1;
        }

    // create an unnamed Second pipe
        if (pipe(secondpipe) == -1) 
    {
            fprintf(stderr, "parent: Failed to create second pipe\n");
            return -1;
        }

    // Create an unnamed time pipe which will share in order to show time spend between processes
        if (pipe(timepipe) == -1) 
    {
            fprintf(stderr, "parent: Failed to create time pipe\n");
            return -1;
        }


    if((ret=fork())==-1)
        perror("fork");
    else if(ret==0)
    {
                int n=-1;
        printf("Child  ----> %d\n",getpid());

        for(n=0;n<5;n++)
        {
                    nbytes = read(firstpipe[0], readbuffer, sizeof(readbuffer));
                    printf("Received string: %s", readbuffer);
            write(secondpipe[1], temp, strlen(temp)+1);
        }

        gettimeofday(&end,0);
                n = sizeof(struct timeval);

                if( write(timepipe[1],&end,sizeof(struct timeval)) != n )
        {
                fprintf(stderr, "child: Failed to write in time pipe\n");
                        exit(EXIT_FAILURE);
                }

    }
    else
    {
        double switch_time;
                int n=-1;
        printf("Parent  ----> %d\n",getpid());
        gettimeofday(&start,0);
                /* Read in a string from the pipe */

        for(n=0;n<5;n++)
        {
            write(firstpipe[1], string, strlen(string)+1);
            read(secondpipe[0], tempbuffer, sizeof(tempbuffer));
                    printf("Received temp: %s", tempbuffer);
        }

        n = sizeof(struct timeval);
                if( read(timepipe[0],&end,sizeof(struct timeval)) != n )
        {
                fprintf(stderr, "Parent: Failed to read from time pipe\n");
                        exit(EXIT_FAILURE);
                }

        wait(NULL);
        switch_time = ((end.tv_sec-start.tv_sec)*1000000+(end.tv_usec-start.tv_usec))/1000.0;
                printf("context switch between two processes: %0.6lfms\n",switch_time/(5*2));


    }   

    return 0;
}
于 2016-07-13T06:58:18.397 に答える
-2

大まかな見積もりとしてこれだけではないのはなぜですか?

#include <ctime>
#include <cstdio>
#include <sys/time.h>
#include <unistd.h>

int main(int argc, char **argv) {
        struct timeval tv, tvt;
        int diff;
        gettimeofday(&tv, 0);
        diff = tvt.tv_usec - tv.tv_usec;
        if (fork() != 0) {
                gettimeofday(&tvt, 0);
                diff = tvt.tv_usec - tv.tv_usec;
                printf("%d\n", diff);
        }
        return 0;
}

注: 実際には、2 番目の引数として null を指定するべきではありません。man gettimeofday を確認してください。また、tvt.tv_usec > tv.tv_usec かどうかを確認する必要があります。ただのドラフト。

于 2011-10-02T16:49:23.920 に答える