Как измерить накладные расходы на переключение контекста очень большой программы?

Я пытаюсь измерить влияние планировщика ЦП на большую программу ИИ (https://github.com/mozilla/DeepSpeech).

Используя strace, я вижу, что он использует много (~ 200) потоков процессора.

Я попытался измерить это с помощью Linux Perf, но мне удалось найти только количество событий переключения контекста, а не их накладные расходы.

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

Как я могу это сделать?


person Azuresonance    schedule 22.02.2021    source источник
comment
Вы пытаетесь измерить общее время в ядре (т.е. включая время, которое ядро ​​выполняет ввод-вывод) или только время, затрачиваемое на переключение контекста?   -  person Noah    schedule 03.03.2021
comment
@Noah Как раз время переключения контекста. Моя конечная цель такова: у меня есть программа, которая проводит много времени в ядре, и мне нужна подробная разбивка этого времени (т. е. x процентов, потраченных на системные вызовы, y процентов, потраченных на переключения контекста и т. д.). Я знаю, что время системного вызова можно измерить strace. Но я не знаю, как измерить время переключения контекста.   -  person Azuresonance    schedule 03.03.2021
comment
Включая время переключения контекста для syscall или только между потоками?   -  person Noah    schedule 03.03.2021


Ответы (1)


Вы уверены, что большинство из этих 200 потоков на самом деле ожидают одновременного запуска, а не ждут данных от системного вызова? Я думаю, вы можете сказать из perf stat, что контекстные переключения на самом деле довольно высоки, но часть вопроса заключается в том, высоки ли они для потоков, выполняющих критическую работу.

Стоимость переключения контекста отражается в промахах кеша после повторного запуска потока. (И не позволяет OoO exec найти такое количество ILP прямо на границе прерывания). Эта стоимость более значительна, чем стоимость кода ядра, который сохраняет/восстанавливает регистры. Таким образом, даже если бы был способ измерить, сколько времени процессоры тратят на код переключения контекста ядра (это возможно с профилировщиком выборки perf record, если ваша настройка perf_event_paranoid позволяет записывать адреса ядра), это не было бы точным отражением истинного Стоимость.

Даже выполнение системного вызова имеет аналогичные (но меньшие и более частые) потери производительности из-за сериализации OoO exec, а также нарушения кэшей (и TLB). В статье Livio & Stumm есть полезная характеристика этого для реальных современных процессоров (с 2010 г.), особенно график на первой странице IPC (количество инструкций за цикл), который падает после возврата системного вызова и требует времени для восстановления: FlexSC: гибкое планирование системных вызовов с системными вызовами без исключений. (Презентация конференции: https://www.usenix.org/conference/osdi10/flexsc-flexible-system-call-scheduling-exception-less-system-calls)


Вы можете оценить стоимость переключения контекста, запустив программу в системе с достаточным количеством ядер, чтобы вообще не нуждаться в переключении контекста (например, большой многоядерный Xeon или Epyc), по сравнению с меньшим количеством ядер, но с теми же процессорами / кэши/межъядерные задержки и тд. Итак, в той же системе с taskset --cpu-list 0-8 ./program, чтобы ограничить количество ядер, которые она может использовать.

Посмотрите на общее количество использованных ЦП в секундах пользовательского пространства: большее количество — это дополнительное время ЦП, необходимое из-за замедления из-за переключения контекста. Время настенных часов, конечно, будет выше, когда одна и та же работа должна конкурировать за меньшее количество ядер, но perf stat включает выходные данные часов задачи, которые сообщают вам общее время в миллисекундах ЦП, которое потоки вашего процесса потратили на ЦП. Это было бы постоянным для того же объема работы, с идеальным масштабированием для большего количества потоков и/или для тех же потоков, конкурирующих за большее/меньшее количество ядер.

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

person Peter Cordes    schedule 22.02.2021
comment
Вы правы, потоки могут ожидать данных и добровольно уступать ЦП, вызывая переключение контекста. Есть ли способ отличить этот случай от непроизвольного переключения контекста, вызванного прерываниями таймера? Я не смог найти способ подсчета прерываний таймера... - person Azuresonance; 22.02.2021
comment
@Azuresonance: подсчет прерываний таймера в любом случае бесполезен, поскольку, если вы не используете бестактное ядро, не каждое прерывание таймера вызывает переключение контекста. Вы можете получить приблизительное представление, взглянув на среднюю загрузку (uptime или top), чтобы увидеть, сколько задач в среднем выполняется или выполнялось бы, если бы у них был доступный ЦП. Но я думаю, что это также учитывает задачи в состоянии D (спящий режим диска); IDK, если это фактор. - person Peter Cordes; 22.02.2021
comment
@Azuresonance: я не знаю, как считать непроизвольные переключения контекста. Например, вы могли бы взять perf stat количество и вычесть количество yield() или futex() системных вызовов? И, конечно же, сон, потому что вам нужно дождаться блокировки, может означать, что какой-то другой поток перешел в спящий режим, удерживая блокировку, или же он просто занимает много времени внутри своей критической секции. Спящий режим с удерживанием блокировки увеличит количество переключений контекста, вызванных кодом, пытающимся использовать больше ядер, чем у вас есть. (Наиболее хороший многопоточный код выбирает количество потоков на основе доступных ядер) - person Peter Cordes; 22.02.2021
comment
Аналогично вашему методу записи производительности, вы можете изолировать начало/конец с помощью PERF_SAMPLE_ADDR. Я не уверен, как вы получаете адрес кода переключения контекста. - person Noah; 22.02.2021
comment
@Noah: Да, perf record использует PERF_SAMPLE_ADDR, если perf_event_paranoid это позволяет. Код переключения контекста в Linux — switch_to(), вызываемый schedule() внутренними элементами переключения контекста. (Однако фактическое сохранение/восстановление регистров пользовательского пространства осуществляется при входе/выходе ядра, за исключением регистров SIMD/FP, которые охотно сохраняются/восстанавливаются при переключении контекста в современных Linux, по крайней мере, для x86. Так что switch_to() просто должен изменить текущий целочисленные регистры, включая указатель стека ядра, сохранение/восстановление состояния FP и таблицу страниц CR3, если новый процесс, а не только поток) - person Peter Cordes; 23.02.2021
comment
@PeterCordes Итак, я попробовал ваш совет perf stat увеличить количество yield и получил очень странный результат, который не имеет никакого смысла. Я запустил perf stat 'syscalls:sys_enter_sched_yield' -e 'context-switches' ./my_program и получил 16 миллионов yield за 3 миллиона переключений контекста. Как может быть больше yield, чем контекстные переключатели? Буду признателен, если у вас есть идеи. - person Azuresonance; 24.02.2021
comment
@Azuresonance: если нет других готовых к запуску задач (которые еще не запущены на другом ядре), yield вернется к той же задаче, которая ее вызвала, без фактического переключения контекста. Или по какой-либо другой причине планировщик решает продолжить выполнение этой задачи. - person Peter Cordes; 24.02.2021