Вопрос или проблема
Я запускаю простую программу, в которой:
- Поток, закрепленный за ЦП 1, выполняет случайные чтения из предварительно выделенной и инициализированной области памяти объемом 2 ГБ, при этом никаких системных вызовов не производится в цикле доступа к памяти.
- Perf выполняется в своем отдельном процессе, измеряя
mem_inst_retired.all_loads:k,mem_inst_retired.all_stores:k -I 200 -p <pid>
Вот минимальный тестовый код:
void access_memory(char *memory) {
// Закрепление потока за ЦП 1
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(1, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
std::mt19937 gen(std::random_device{}());
std::uniform_int_distribution<size_t> dist(0, 2GB - 500);
char buffer[500];
while (!should_stop) {
size_t offset = dist(gen);
memcpy(buffer, memory + offset, 500);
buffer[0]++;
}
}
Вопросы:
- Почему происходят операции с памятью ядра, когда процесс только выполняет чтения памяти из пользовательского пространства? Хотя это может быть наблюдательным эффектом, могут ли прерывания, сгенерированные PMU (Устройством мониторинга производительности) для выборки, вызывать выполнение операций с памятью ядром, которые приписываются процессу доступа? Я пролистал SDM, но не нашел хорошего ответа.
- Как perf приписывает операции в режиме ядра конкретному процессу? В случае системных вызовов ясно, что ядро работает “от имени” процесса, но как насчет задач ядра, таких как планирование, компактация памяти или балансировка нагрузки, которые могут повлиять на процесс? Где он проводит грань?
Я замечаю, что изменение интервала выборки с 200 мс до 5 с изменяет количество измеренных операций ядра с ~10^5 до ~10^7 операций на интервал.
Вот простой код, который выполняет доступы и измеряет активность загрузки/записи:
https://gist.github.com/VinayBanakar/8cd04c5fa03a6895292498d3e3687aac
Обработчики прерываний и исключений не переключаются контекстом с current
задачи (пока/если планировщик не решит, что они это делают), поэтому аппаратные счётчики производительности остаются запрограммированными (в вашем случае для подсчета инструкций записи ядра) во время выполнения обработчиков прерываний, вызванных таймерами и внешним оборудованием.
Страницы ошибок – это распространённый тип исключений, который вызывает выполнение кода ядра. Например, обнуление новых страниц стека, BSS или кучи (mmap(MAP_ANONYMOUS)
), когда вы впервые их записываете. (Если первый доступ – это чтение, то оно будет копировать по ссылке на запись на системную общую страницу нулей.) Итак, у вас, вероятно, есть несколько страниц ошибок, которые предполагают хранение целой страницы нулей ядром, когда вы используете новое пространство стека, но в остальном ваши разбросанные чтения должны просто приводить к записям в структуры данных таблицы страниц, и накладным расходам на учёт ядра и вызовам функций.
Или если вы выполняете инициализацию до того, как perf
подключается, тогда, вероятно, вы не будете измерять ни одной страницы ошибок.
Подсистема perf в Linux виртуализирует PMU (когда не в режиме полной системы) так же, как она виртуализирует другой контекст процесса, сохраняя/восстанавливая при переключении контекста. Переменная current
, относящаяся к ядру, указывает на задачу, которая в данный момент выполняется на этом ядре.
perf stat
генерирует минимальное количество прерываний, так как счетчики могут быть запрограммированы с таким высоким лимитом, насколько они поддерживают, поэтому события переполнения происходят очень редко. В отличие от perf record
, где вы хотите достаточно частые прерывания для сбора значимой статистики.
Ответ или решение
Причины операций с памятью ядра в perf stats для процессов только в пользовательском пространстве
Введение
Понимание того, почему происходят операции с памятью ядра, когда вы работаете с процессом, осуществляющим только пользовательские операции, является ключевым элементом оптимизации производительности приложений. В данной статье мы разберем причины, по которым в статистике perf
могут отображаться операции с памятью ядра, даже когда ваше приложение только читает данные из памяти, без совершения системных вызовов.
Описание проблемы
Вы используете простой тест, в котором поток, зафиксированный на ядре CPU 1, производит произвольные чтения из заранее выделенной области памяти размером 2 ГБ. В то же время вы запускаете perf
в отдельном процессе для измерения операций загрузки и записи в память. Несмотря на то, что ваш процесс не выполняет системные вызовы, в статистике perf
фиксируются операции памяти ядра.
Причины операций с памятью ядра
-
Обработчики прерываний и исключений: Во время выполнения кода ваш процесс может сталкиваться с аппаратными прерываниями и программными исключениями. Например, если ваш процесс использует многопоточность или взаимодействует с другими компонентами операционной системы, это может вызывать прерывания. Обработчики этих прерываний могут выполнять операции, которые изменяют состояние памяти, включая записи в структуру таблицы страниц или ведение учета, что может привести к увеличению количества записей памяти в ядре.
-
Обращения к памяти: При доступе к памяти могут возникать_page faults (ошибки страниц), особенно если происходит обращение к ранее не инициализированным или не выгруженным страницам памяти. Ядро может выполнять операции по обнулению новых страниц (например,
mmap(MAP_ANONYMOUS)
), что также приведет к увеличению операций записи в память ядра. -
Работа с дескрипторами и резервированием ресурсов: Даже при отсутствии явных системных вызовов, операционная система может выполнять внутренние операции по управлению памятью и ресурсами для вашего процесса. Эти операции могут включать изменения в динамически управляемых структурах данных, требующих записи в память ядра.
-
Счётчики производительности: Когда вы используете
perf
, программируемые счетчики производительности представляют собой дополнительное программное обеспечение, которое самостоятельно вызывает прерывания для обработки и хранения метрик. Эти прерывания могут вызывать дополнительные накладные расходы в памяти ядра, соответствующие приготовлению к выборке образцов и записи данных.
Добавленные детали о perf
-
Атрибуция операций ядра: Система
perf
атрибутирует операции, происходящие в ядре, определенным процессам путем отслеживания контекста выполнения. В случае выполнения прерываний или задач ядра привязанных к вашему процессу,perf
может учитывать это при подсчете операций — например, для планирования, сбалансированного распределения нагрузки и других фоновый процессов, затрагивающих ваш процесс. -
Интервалы выборки: Как видно из вашего эксперимента с изменением интервала выборки с 200 мс до 5 с, значительное увеличении операций, зарегистрированных в памяти ядра, указывает на аномалии больших промежутков времени, когда могут всплывать фоновые задачи или процессы управления памятью. Более частая выборка, скорее всего, приводит к более точным и менее шумным результатам.
Заключение
Исходя из вышеизложенного, операции с памятью ядра, фиксируемые в perf
, даже при выполнении стендового пользовательского кода, имеют множество причин. Это включает в себя интервенции ядра, обработку прерываний, ошибки страниц и работу системы производительности. На понимании этих механизмов основывается возможность оптимизации вашей программы и улучшения её производительности. Определение и анализ затрат на операции с памятью ядра остаются важными шагами на пути к разработке высокоэффективных программных решений.