Точность 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
и так далее, когда я проверяю время, возвращаемое в начальную и конечную структуры timepec, я вижу, что tv_nsec также представляется "округленным" числом:
start.tv_nsec: 714885000, end.tv_nsec: 714888000
Будет ли это вызвано тем, что clock_monotonic недостаточно точен для того, что я пытаюсь измерить, или какой-то другой проблемой, которую я пропускаю?
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 наносекунд могут отражать временные интервалы, а не время переключения.
Ваш основной поток записывает нулевые символы в конец записи канала, но, кажется, ничего не читает их. Если действительно нет, то это в конечном итоге заполнит буфер и блок канала. Цель потеряна для меня.