Подтвердить что ты не робот

Напишите программу на C, чтобы измерить время, проведенное в контекстном коммутаторе в ОС Linux

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

Контекстный переключатель происходит примерно так:

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

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

Ответ 5

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

Ответ 6

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

#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! Просто черновик.