Напишите программу на C для измерения времени, потраченного на переключение контекста в ОС Linux - PullRequest
27 голосов
/ 03 марта 2010

Можем ли мы написать программу на c, чтобы узнать время, потраченное на переключение контекста в Linux? Не могли бы вы поделиться кодом, если у вас есть? Спасибо

Ответы [ 6 ]

25 голосов
/ 03 марта 2010

Профилирование времени переключения очень сложно, но вам помогут инструменты профилирования задержки в ядре, а также 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";
}

Результаты в Ubuntu 2.6.32-14-generic kernel. Во время измерения я компилировал код 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 мс.

Легенда в распечатке, похоже, страдает от ошибки округления или чего-то еще (и вставленный исходный код не является той же версией). Я никогда не полировал это приложение для релиза ...

8 голосов
/ 03 марта 2010

Если у вас есть привилегии суперпользователя, вы можете запустить программу SystemTap с контрольными точками для переключения контекста и напечатать текущее время для каждого:

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

Я не уверен, насколько надежны выходные данные, но это быстрый и простой способ получить некоторые цифры.

5 голосов
/ 19 ноября 2011

Как вы думаете, измеряя переключение контекста с секундами или миллисекундами или даже микросекундами. Все происходит меньше, чем нано-сек. Если вы хотите потратить столько времени на переключение контекста, которое можно измерить, то ... Попробуйте какой-нибудь код типа ядра реального режима, написанный на ассемблере, вы можете кое-что увидеть.

4 голосов
/ 03 марта 2010

Краткий ответ - нет. Длинный ответ ниже.

Переключение контекста происходит примерно так:

  1. Пользовательский процесс входит в ядро ​​через системный вызов или прерывание (например, сбой страницы), а запрошенные данные (например, содержимое файла) еще не доступны, поэтому ядро ​​переводит указанный пользовательский процесс в состояние сна и переключается на другой выполняемый процесс.
  2. Ядро обнаруживает, что данный пользовательский процесс использовал свои кванты полного времени (это происходит в коде, вызываемом из прерывания таймера.)
  3. Данные становятся доступными для процесса с более высоким текущим приоритетом, который в настоящее время находится в спящем режиме (это происходит из кода, вызванного из / вокруг прерываний ввода-вывода.)

Сам переключатель является односторонним, поэтому лучшее, что мы можем сделать в пользовательской среде (я полагаю, это то, что вы просите), это измерить RTT, от нашего процесса до другого и обратно. Другой процесс также требует времени, чтобы сделать свою работу. Конечно, мы можем заставить два или более процессов взаимодействовать в этом, но дело в том, что ядро ​​не гарантирует, что один из наших процессов будет выбран следующим. Вероятно, возможно предсказуемо переключиться на данный процесс с помощью RT-планировщика, но я не советую здесь, предложения приветствуются.

2 голосов
/ 13 июля 2016

Измерение стоимости переключения контекста немного сложнее. Мы можем вычислить время, потраченное на переключение контекста, запустив два процесса на одном процессоре и установив три канала Linux между ними;

  • две трубы для разделения строки между процессом и
  • 3-й будет использоваться, чтобы поделиться временем, проведенным в дочернем процессе.

Затем первый процесс выдает запись в первый канал и ожидает чтения во втором; увидев первый процесс, ожидающий что-то для чтения из второго канала, ОС переводит первый процесс в заблокированное состояние и переключается на другой процесс, который читает из первого канала и затем записывает во второй. Когда второй процесс снова пытается прочитать данные из первого канала, он блокируется, и, таким образом, цикл обмена данными продолжается. Измеряя стоимость повторного общения, вы можете оценить стоимость переключения контекста.

Одна сложность при измерении стоимости переключения контекста возникает в системах с более чем одним ЦП; в такой системе нужно убедиться, что процессы переключения контекста расположены на одном процессоре , К счастью, в большинстве операционных систем есть вызовы для привязки процесса к определенному процессору; в Linux, например, вызов sched_setaffinity () - это то, что вы ищете. Обеспечивая, чтобы оба процесса работали на одном процессоре, вы измеряете стоимость остановки ОС одним процессом и восстановления другого на том же процессоре.

Здесь я публикую свое решение для вычисления переключения контекста между процессами.

    #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;
}
0 голосов
/ 02 октября 2011

Почему бы не просто это как грубая оценка?

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

Примечание: На самом деле мы не должны указывать значение null в качестве второго аргумента, проверьте man gettimeofday. Кроме того, мы должны проверить, если tvt.tv_usec> tv.tv_usec! Просто черновик.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...