Точность clock_gettime() в сценарии переключения контекста

Я пытаюсь «примерно» рассчитать время переключения контекста потока в системе Linux. Я написал программу, которая использует каналы и многопоточность для достижения этой цели. При запуске программы расчетное время явно неверно (см. вывод ниже). Я не уверен, связано ли это с тем, что я использую неправильный clock_id для этой процедуры или, возможно, с моей реализацией.

Я реализовал sched_setaffinity(), чтобы программа работала только на ядре 0. Я пытался оставить как можно больше пуха в коде, чтобы измерять только время переключения контекста, поэтому процесс протектора записывает только один символ в канал, а родитель читает 0 байт.

У меня есть родительский поток, который создает один дочерний поток с односторонним каналом между ними для передачи данных, дочерний поток выполняет простую функцию для записи в канал.

    void* thread_1_function()
    {
         write(fd2[1],"",sizeof("");
    }

в то время как родительский поток создает дочерний поток, запускает счетчик времени, а затем вызывает чтение в канале, в который пишет дочерний поток.

int main(int argc, char argv[])
{
//time struct declaration
struct timespec start,end;

//sets program to only use core 0
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(0,&cpu_set);


if((sched_setaffinity(0, sizeof(cpu_set_t), &cpu_set) < 1))
{

int nproc = sysconf(_SC_NPROCESSORS_ONLN);
int k;

printf("Processor used: ");
for(k = 0; k < nproc; ++k)
{
    printf("%d ", CPU_ISSET(k, &cpu_set));
}

printf("\n");


if(pipe(fd1) == -1)
{
    printf("fd1 pipe error");
    return 1;
}
//fail on file descriptor 2 fail
if(pipe(fd2) == -1)
{
    printf("fd2 pipe error");
    return 1;
}


pthread_t thread_1;


pthread_create(&thread_1, NULL, &thread_1_function, NULL);


pthread_join(thread_1,NULL);


int i;
uint64_t sum = 0;

for(i = 0; i < iterations; ++i)
{

    //initalize clock start
    clock_gettime(CLOCK_MONOTONIC, &start);
    //wait for child thread to write to pipe
    read(fd2[0],input,0);
    //record clock end
    clock_gettime(CLOCK_MONOTONIC, &end);   

    write(fd1[1],"",sizeof(""));



    uint64_t diff;
    diff = billion * (end.tv_sec - start.tv_sec) + end.tv_nsec - start.tv_nsec;
    diff = diff;
    sum += diff;
}

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

     3000
     3000
     4000
     2000
     12000
     3000
     5000

и т. д., когда я проверяю время, возвращенное в структуры start и end timespec, я вижу, что tv_nsec также кажется «округленным» числом:

     start.tv_nsec: 714885000, end.tv_nsec: 714888000

Будет ли это вызвано тем, что clock_monotonic недостаточно точен для того, что я пытаюсь измерить, или какой-то другой проблемой, которую я упускаю из виду?


person moose0306    schedule 30.01.2019    source источник
comment
Можете ли вы показать свой код (см. минимальный, полный и проверяемый пример). Необычно использовать трубу между нитями. Вы рассматриваете потоки или разветвленные процессы?   -  person Alain Merigot    schedule 30.01.2019
comment
У меня возникли проблемы с пониманием частей вашей формулировки. Например, кажется, что вы используете термины поток и процесс взаимозаменяемо, но это разные вещи. И мне очень трудно сопоставить tv_nsec с теми же, хотя и в большем масштабе, с данными, которые вы на самом деле представляете.   -  person John Bollinger    schedule 30.01.2019
comment
В целом, я не уверен в основании вашего заявления о том, что расчетное время явно неверно, но также не уверен в том, почему вы думаете, что ваш подход (который вы, похоже, не полностью раскрыли) служит для измерения времени переключения контекста. Они связаны между собой, поскольку, если вместо этого вы на самом деле измеряете что-то другое, то любые ваши ожидания относительно результатов, вероятно, неверны.   -  person John Bollinger    schedule 30.01.2019
comment
отредактировано, чтобы добавить больше кода, и скорректировано для ясности между процессом и потоком.   -  person moose0306    schedule 30.01.2019
comment
Мое утверждение исходит из использования clock_gettime в предыдущих обучающих приключениях, и оно никогда не возвращается с тем, что я считаю неточным числом, то есть с количеством завершающих нулей, что заставляет меня чувствовать, что это округленное число (что я знаю из документации это не)   -  person moose0306    schedule 30.01.2019
comment
@AlainMerigot, я открыт либо для разветвленного процесса, либо для потоков. Это просто учебный пример для меня, чтобы лучше понять, что происходит.   -  person moose0306    schedule 30.01.2019
comment
Вы компилируете свою программу в 64-битном режиме (например, x86_64)? В противном случае это повлияет на rvalue расчета diff. Каков точный тип/определение billion? Это [надеюсь] тоже uint64_t?   -  person Craig Estey    schedule 30.01.2019
comment
Чтобы быть уверенным, вы можете вызвать clock_getres, чтобы убедиться, что clock_gettime использует наносекундную точность. (например) linux будет использовать его, как и большинство других ОС POSIX (например, *BSD, MacOSX), но, поскольку у вас возникают проблемы ... CLOCK_MONOTONIC [если поддерживается, что во многих местах] похоже на CLOCK_REALTIME, за исключением того, что он никогда не идет назад во времени. Вы можете попробовать это с обоими вариантами, чтобы увидеть.   -  person Craig Estey    schedule 31.01.2019


Ответы (1)


я вижу, что tv_nsec также кажется «округленным» числом:

 2626, 714885000, 2626, 714888000

Будет ли это вызвано тем, что clock_monotonic недостаточно точен для того, что я пытаюсь измерить, или какой-то другой проблемой, которую я упускаю из виду?

Да, это возможность. Все часы, поддерживаемые системой, имеют фиксированное разрешение. struct timespec может поддерживать часы с разрешением в наносекунды, но это не означает, что вы можете ожидать, что все часы действительно имеют такое разрешение. Похоже, что ваш CLOCK_MONOTONIC может иметь разрешение 1 микросекунда (1000 наносекунд), но вы можете проверить это с помощью функции clock_getres().

Если он доступен для вас, вы можете попробовать CLOCK_PROCESS_CPUTIME_ID. Вполне возможно, что для вас это будет иметь более высокое разрешение, чем CLOCK_MONOTONIC, но обратите внимание, что разрешение в одну микросекунду довольно точно — это порядка одного тика на 3000 циклов ЦП на современной машине.


Тем не менее, я вижу несколько возможных проблем с вашим подходом:

  • Хотя вы настроили свой процесс на привязку к одному ЦП, это не мешает системе также планировать другие процессы на этом ЦП. Таким образом, если вы не предприняли дополнительных мер, вы не можете быть уверены — это даже маловероятно — что каждое переключение контекста с одного из потоков вашей программы на другой поток.

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

  • read() со счетчиком 0 может проверять наличие ошибок, а может и не проверять, и уж точно не передает никаких данных. Мне совершенно непонятно, почему вы отождествляете время этого звонка со временем переключения контекста.

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

  • Ваш основной поток записывает нулевые символы в конец записи канала, но, похоже, их ничего не читает. Если его действительно нет, то это в конечном итоге заполнит буфер канала и заблокирует его. Цель потеряна для меня.

person John Bollinger    schedule 30.01.2019