четверг, 11 апреля 2013 г.

PCPU счётчики в ядре

Несколько дней назад Глеб Смирнов (glebius@) добавил в ядро FreeBSD 10-CURRENT новое API для работы со счётчиками - counter(9). Счётчики в системе используются для ведения статистики. Обычно это структура данных, значения полей которой изменяются при возникновении тех или иных событий. Например, получение сетевого пакета, обнаружение некорректных данных, подсчёт количества определённых действий совершённых системой и т.п. Некоторые счётчики изменяются относительно редко, а некоторые могут изменяться миллионы раз в секунду. Ядром используется обычно два способа их изменения - это простой инкремент (операция добавления) или атомарное добавление (atomic(9)). 
У обоих методов есть недостатки. Обычный инкремент на многопроцессорных/многоядерных системах приводит к потере данных, т.е. когда несколько ядер одновременно изменяют одну и ту же переменную, выполнить это удаётся, грубо говоря, только одному ядру. Поэтому статистика временами заметно не соответствует действительности.
Атомарные операции не теряют данные благодаря блокированию шины памяти, что значительно сказывается на скорости выполнения. 
Общим недостатком является то, что изменение счётчика приводит к инвалидированию cache line, в которой находится счётчик из кешей процессоров, что опять же отрицательно сказывается на производительности.
Теперь о том, как эти проблемы решает новое API. Для каждого ядра процессора выделяется своя область памяти, а счётчики преобразуются таким образом, что каждому ядру соответствует своя копия счётчика. Поэтому, даже при одновременном изменении счётчика несколькими ядрами, каждое ядро будет изменять свой счётчик. При этом не будет происходить блокирование шины памяти, не будет потери данных и не будет вымывания кеша, т.к. каждое ядро обращается к своим данным. 
Чтобы обновить счётчик, нужно вычислить адрес счётчика в памяти приватной для данного ядра, а затем произвести его изменение. Для того чтобы в это время контекст не мигрировал на другое ядро, используются критические секции. Однако, не на всех архитектурах это необходимо и с помощью некоторых know-how удаётся избежать использования критических секций. В частности, на нашей основной архитектуре amd64 обновление счётчика осуществляется в одну процессорную инструкцию!
Для того чтобы прочитать значение счётчика, нужно сложить значения копий счётчика со всех ядер. Ну и объём памяти, занимаемый счётчиком, конечно же стал в несколько раз больше (в зависимости от числа ядер процессоров).
Это, на мой взгляд, приемлемая плата вот за какие результаты:
  • в ряде тестов новые счётчики показывают повышение производительности примерно на 63% по сравнению с обычной операцией инкремента, но при этом нет потерь данных, а обычный инкремент теряет 98% данных;
  • в сравнении с атомарными инкрементами новые счётчики в 22 раза быстрее;
  • на реальных тестах по приёму сетевого трафика замена счётчиков IP статистики приводит к снижению нагрузки на CPU примерно на 50%.