Red Hat Training

A Red Hat training course is available for Red Hat Enterprise Linux

Глава 4. Процессор

Современные компьютеры могут быть оборудованы несколькими процессорами, которые представляют собой интегральные схемы, вставляемые в специальный разъем (сокет) материнской платы. Сокет соединяется с другими сокетами, контроллерами памяти и другими периферийными устройствами. С точки зрения операционной системы сокет логически объединяет процессоры и их ресурсы.
Red Hat Enterprise Linux включает множество инструментов для отслеживания активности процессора. Полученная информация помогает разработать стратегию оптимизации производительности (см. Раздел 4.1.2, «Коррекция производительности процессора»).

Топология

Раньше компьютеры были оборудованы меньшим числом процессоров, что допускало использование симметричной многопроцессорной схемы доступа к памяти (SMP, Symmetric Multi-Processor). Со временем число процессоров на сокет увеличивалось, и стоимость такой схемы значительно возросла. На сегодняшний день многопроцессорные системы используют неравномерный доступ к памяти (NUMA, Non-Uniform Memory Access).
Процессоры AMD уже раньше имели такую инфраструктуру за счет технологии HT (Hyper Transport), в то время как Intel начал внедрять возможности NUMA в схемы QPI (Quick Path Interconnect). Конфигурация SMP и NUMA отличается, так как при выделении ресурсов должна учитываться топология системы.

Потоки

В терминологии Linux поток — абстракция для обозначения передачи данных. Потоку выделяется сегмент исполняемого кода, который он может выполнять на процессоре. Планирование обработки потоков на свободных процессорах осуществляется на уровне операционной системы.
Операционная система распределяет нагрузку между процессорными ядрами, тем самым стараясь максимизировать занятость процессора, но это не означает, что производительность приложений также улучшится. Например, перенос потока на процессор в другой сокет вместо того, чтобы дождаться освобождения текущего процессора, только замедлит работу. Это следует учесть при проектировании высокопроизводительных приложений (см. Раздел 4.2, «Планирование занятости процессоров»).

Прерывания

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

4.1. Топология процессоров

4.1.1. Топология NUMA

Изначально компьютеры были оборудованы одним процессором. Иллюзия параллельных вычислений создавалась на уровне операционной системы за счет переключения потоков. Однако увеличение скорости обработки растущего числа заданий не могло продолжаться бесконечно, поэтому впоследствии стали добавляться дополнительные процессоры, что позволило на самом деле реализовать возможности параллелизма.
Ранние многопроцессорные системы использовали параллельную шину для соединения процессора с памятью, выделяя одинаковые сегменты времени для доступа к памяти. Это носит название симметричной многопроцессорности и подходит для небольшого числа процессоров. Но при увеличении числа процессоров до 8-16 требования к полосе пропускания существенно возрастают, что оставляет меньше места для периферийных компонентов.
Увеличение числа процессоров возможно за счет объединения следующих компонентов:
  1. Последовательные шины.
  2. Топологии NUMA.
Последовательные шины характеризуются высокой скоростью передачи пакетов данных. Изначально они служили для организации высокоскоростного сообщения между процессорами, между процессорами и контроллерами памяти, и другими периферийными компонентами. Теперь одна шина может объединять 32-64 соединений, что существенно сокращает место на плате.
Со временем вместо подключения отдельных процессоров к плате разработчики стали объединять ядра процессоров в один модуль, а вместо предоставления равноправного доступа к памяти была разработана стратегия неравномерного доступа (NUMA, Non-Uniform Memory Access). В этой схеме реализации компьютерной памяти каждому сокету выделяется отдельный сегмент памяти для высокоскоростного доступа. Дополнительно сокеты соединены друг с другом, что позволяет им обращаться к памяти других сокетов.
В качестве простого примера рассмотрим плату с двумя сокетами, в каждый из которых подключен 4-ядерный процессорный модуль. Таким образом, общее число процессоров составляет 8. Каждый сокет соединен с модулем памяти размером 4 ГБ, то есть общий объем ОЗУ составляет 8 ГБ. Представим, что процессоры 0-3 размещены в сокете 0, а 4-7 в сокете 1. Дополнительно каждый сокет сопоставлен отдельному узлу NUMA.
Для доступа процессора c номером 0 к памяти из банка 0 требуется 3 такта: один такт для передачи адреса контроллеру памяти, второй для установки доступа к памяти, третий — для выполнения операции записи или чтения. В то же время для доступа процессора 4 потребуется 6 тактов, так как он расположен в другом сокете, что добавляет две операции — обращение к контроллеру локальной памяти в сокете 1 и обращение к контроллеру удаленной памяти в сокете 0. Если в это время к памяти обращается другой процессор, контроллеры должны будут установить порядок доступа, что, естественно, замедлит работу. Синхронизация кэша процессора с памятью еще больше усложняет задачу.
Последние модели процессоров Intel (Xeon) и AMD (Opteron) используют функции NUMA. Роль межпроцессорной шины для процессоров AMD выполняет HyperTransport (HT), а для Intel — QuickPath Interconnect (QPI).
Так как системные архитектуры могут значительно отличаться, точно предсказать снижение производительности вследствие обращения к памяти в других сокетах довольно сложно. Каждое межпроцессорное соединение увеличивает задержку. Так, если для доступа к памяти надо осуществить два перехода по шине, формула расчета времени доступа будет выглядеть так: 2N + время доступа к памяти (где N — время задержки при подключении к шине).
Принимая это во внимание, высокопроизводительные приложения должны избегать доступа к удаленной памяти в схемах с топологией NUMA, предпочитая локальную память.
Для этого при проектировании приложений надо ответить на следующие вопросы:
  1. Какова топология системы?
  2. Где выполняется приложение?
  3. Где размещен ближайший банк памяти?

4.1.2. Коррекция производительности процессора

В этой секции рассматриваются способы коррекции производительности процессора.
Изначально схема NUMA предназначалась для организации доступа одного процессора к разным банкам памяти. Позднее появились многоядерные процессоры, где ядра получают равные отрезки времени для доступа к локальной памяти и могут совместно использовать кэш. Недостаток такого подхода состоит в том, что каждое подключение к памяти, ядру и кэшу на другом сокете добавляет небольшую задержку.
Рисунок 4.1, «Локальный и удаленный доступ к памяти» демонстрирует два узла NUMA. Каждый узел включает 4 процессора, контроллер и банк памяти. Последовательность действий при обращении процессоров с узла 1 к памяти:
  1. Процессор (ядро 0-3) передает адрес памяти локальному контроллеру.
  2. Контроллер устанавливает доступ к памяти.
  3. Процессор выполняет операции чтения и записи в область памяти с заданным адресом.
The CPU icon used in this image is part of the Nuvola 1.0 (KDE 3.x icon set), and is held under the LGPL-2.1: http://www.gnu.org/licenses/lgpl-2.1.html

Рисунок 4.1. Локальный и удаленный доступ к памяти

Если процессор на одном узле обращается к банку памяти на другом узле, цикл выполнения будет таким:
  1. Процессор (ядро 0-3) передает адрес памяти удаленному контроллеру.
    1. Удаленный контроллер памяти получает запрос доступа.
  2. Контроллер устанавливает доступ к локальному банку памяти.
  3. Процессор выполняет операции чтения и записи область памяти с заданным адресом.
Таким образом, обращение к удаленной памяти может занять почти в два раза больше времени. При проектировании высокопроизводительных приложений такие соединения следует минимизировать.
При этом следует учитывать:
  • топологию системы (схему связи компонентов);
  • процессорное ядро, на котором запускается программа;
  • расположение банка памяти.
Red Hat Enterprise Linux 6 предоставляет инструменты для коррекции производительности систем NUMA. Они будут рассмотрены ниже.

4.1.2.1. taskset

taskset позволяет привязать процесс к определенному процессору. Недостаток состоит в том, что taskset не гарантирует выделение локальной памяти. Эту функцию успешно выполняет numactl (см. Раздел 4.1.2.2, «numactl»).
Процессоры определяются с помощью маски. Минимальное значение соответствует первому процессору, максимальное — последнему. Например, 0x00000001 может обозначать процессор с номером 0, а 0x00000003 — процессоры 0 и 1.
Ниже приведена команда привязки работающего процесса к процессору, заданному с помощью маски.
# taskset -p маска PID
Следующая команда привяжет заданную программу к процессору. Значение программа может содержать ее имя и аргументы.
# taskset маска -- программа
Аргумент -c позволяет определить список или диапазон процессоров:
# taskset -c 0,5,7-9 -- myprogram
Подробную информацию можно найти на справочной странице man taskset.

4.1.2.2. numactl

numactl выполняет процессы в соответствии с политикой распределения памяти. Политика определяет правила для заданного процесса и его дочерних процессов. numactl получает топологию системы из /sys.
Файловая система /sys содержит схему соединения процессоров, памяти и периферийных устройств. Так, например, /sys/devices/system/cpu содержит информацию о процессорах, а /sys/devices/system/node — об узлах NUMA и их взаимном расположении.
В системах NUMA расстояние между процессором и памятью имеет огромное значение — чем дальше они расположены, тем медленнее доступ. Поэтому при проектировании требовательных к производительности приложений следует выбирать память из наиболее близкого банка.
Высокопроизводительные приложения — особенно многопоточные — должны выполняться на нескольких процессорных ядрах. Так как размер кэша первого уровня небольшой, то при выполнении нескольких потоков на одном ядре не исключена вероятность того, что один поток вытеснит из кэша данные, используемые предыдущим потоком. Таким образом, часть времени будет затрачена на поочередную запись в кэш. Чтобы этого не случилось, рекомендуется привязать приложение не к одному процессорному ядру, а к узлу, что позволит потокам совместно использовать кэш на разных уровнях. Исключение составляют приложения, использующие одни и те же данные в кэше, — их производительность при выполнении на одном процессорном ядре не пострадает.
numactl позволяет привязать приложение к одному процессорному ядру или узлу NUMA и выделить локальную память. Доступные параметры включают:
--show
Возвращает настройки правил NUMA для текущего процесса. Пример: numactl --show.
--hardware
Возвращает список доступных узлов.
--membind
Выделяет память только на заданных узлах. Если памяти недостаточно, команда вернет ошибку. Формат: numactl --membind=список_узлов программа. Список узлов может содержать разделенные запятой номера узлов и диапазоны. Подробную информацию можно найти на справочной странице man numactl.
--cpunodebind
Выполнение программы и дочерних процессов на процессорах, принадлежащих указанным узлам. Формат: numactl --cpunodebind=список_узлов программа. Список узлов содержит разделенные запятой номера узлов и диапазоны. Подробную информацию можно найти на справочной странице man numactl.
--physcpubind
Выполнение программы и дочерних процессов на указанных процессорах. Формат: numactl --physcpubind=список_процессоров программа. Список содержит разделенные запятой номера процессоров (номера можно получить из /proc/cpuinfo.) Подробную информацию можно найти на справочной странице man numactl.
--localalloc
Память должна выделяться только на текущем узле.
--preferred
Если возможно, память будет выделяться на заданном узле. В случае неудачи будут выбираться другие узлы. Формат: numactl --preferred=номер_узла. Подробную информацию можно найти на справочной странице man numactl.
Пакет numactl включает в свой состав библиотеку libnuma, которая предоставляет интерфейс для создания собственной политики NUMA. За подробной информацией обратитесь к справочной странице man numa(3).

4.1.3. numastat

Важно

numastat был разработан Энди Клином и изначально представлял собой сценарий Perl. Впоследствии он был существенно доработан и добавлен в Red Hat Enterprise Linux 6.4.
Несмотря на то что утилита numastat полностью совместима со своей ранней версией, их параметры и результаты работы могут значительно отличаться.
numastat выводит статистику распределения памяти (включая попадание и промахи) для процессов и операционной системы. По умолчанию numastat покажет число занятых страниц памяти и список событий для каждого узла NUMA.
Оптимальная производительность характеризуется низкими значениями numa_miss и numa_foreign.
numastat также возвращает информацию о распределении памяти между узлами NUMA.
Можно сопоставить результаты numastat и top, чтобы убедиться, что потоки выполняются на тех же узлах, где была выделена память.

Стандартная статистика

numa_hit
Число успешно выделенных страниц на узле.
numa_miss
Число страниц, которые должны были быть выделены на другом узле, но из-за нехватки памяти были выделены на текущем узле. Каждому событию numa_miss соответствует событие numa_foreign на другом узле.
numa_foreign
Число страниц, выделенных на другом узле, которые изначально были предназначены для текущего узла. Каждому событию numa_foreign соответствует событие numa_miss на другом узле.
interleave_hit
Число успешно выделенных страниц с использованием чередования.
local_node
Число успешно выделенных страниц памяти для локального процесса.
other_node
Число страниц, выделенных на этом узле процессу, выполняемому на другом узле.
Далее перечислены параметры, которые в качестве единиц измерения используют мегабайты.
-c
Компактное представление таблицы данных. Обычно используется при наличии большого числа узлов NUMA, однако ширину столбцов и расстояние между ними предсказать невозможно. Размер памяти будет округляться до ближайшего мегабайта.
-m
Возвращает статистику памяти для каждого узла. Формат аналогичен /proc/meminfo.
-n
Возвращает ту же информацию что и исходная версия numastat (numa_hit, numa_miss, numa_foreign, interleave_hit, local_node, other_node) в обновленном формате с использованием мегабайт в качестве единиц измерения.
-p шаблон
Возвращает информацию о распределении памяти в соответствии с заданным шаблоном. Если шаблон содержит цифры, numastat интерпретирует их как идентификатор процесса. В противном случае numastat будет искать совпадение в строках команд.
За параметром -p следуют дополнительные фильтры.
-s
Сортировка результатов по убыванию, то есть процессы, потребляющие больше всего ресурсов в соответствии с содержимым столбца total, будут приведены в начале списка.
Если дополнительно указать узел, таблица будет отсортирована по узлам. Пример:
numastat -s2
Параметр и значение не должны разделяться пробелом.
-v
Подробный вывод.
-V
Возвращает версию numastat.
-z
Исключает строки и столбцы с нулевыми значениями. При этом значения, которые округляются до нуля, не будут отфильтрованы.

4.1.4. numad

numad — средство для привязки процессов к процессорам исходя из топологии NUMA. numad следит за топологией и динамически подстраивается к изменениям конфигурации, тем самым поддерживая должный уровень производительности.
В некоторых случаях numad может улучшить производительность до 50%. numad периодически запрашивает информацию из /proc и пытается поместить критические процессы на узлы со свободными ресурсами, где производительность будет максимальна. Минимальные требования составляют 50% ресурсов одного процессора и 300 МБ памяти. Необходимый уровень производительности поддерживается за счет переноса процессов между узлами NUMA по мере освобождения их ресурсов.
numad также предоставляет рекомендации, которые могут использоваться другими инструментами управления задачами. За более подробной информацией обратитесь к описанию параметра -w на справочной странице man numad.

4.1.4.1. Достоинства numad

numad особенно подходит для продолжительных ресурсоемких процессов, особенно если область их выполнения ограничена лишь подмножеством ресурсов.
numad может улучшить производительность приложений, выполняющихся на нескольких узлах NUMA. Но при росте потребляемых ресурсов эффективность numad падает.
Для коротких процессов, не потребляющих много ресурсов, numad не требуется — так же как и для систем с непоследовательным обращением к памяти.

4.1.4.2. Режимы работы

Примечание

При объединении больших объемов памяти службой KSM статистика ядра может оказаться непоследовательной. В будущих выпусках функции работы с NUMA в KSM будет усовершенствованы, но на сегодняшний день в системах в большими объемами свободной памяти рекомендуется отключить KSM.
numad может работать как:
  • служба,
  • исполняемый модуль.
4.1.4.2.1. numad как служба
Работающая служба numad будет динамически корректировать нагрузку.
Запуск службы:
# service numad start
Чтобы запуск происходил при каждой загрузке системы:
# chkconfig numad on
4.1.4.2.2. numad как исполняемый модуль
Команда запуска:
# numad
numad будет работать, пока он не будет остановлен. События будут регистрироваться в /var/log/numad.log.
Для управления конкретным процессом выполните:
# numad -S 0 -p PID
-p PID
Добавляет заданный процесс в список обработки. Добавленный процесс будет обработан, только если он удовлетворяет минимальным требованиям обслуживания.
-S режим
Параметр -S определяет режим проверки процессов. Так, значение 0 ограничивает управление процессами, включенными в список обработки.
Остановка numad:
# numad -i 0
После остановки numad изменения сопоставлений NUMA не будут отменены. При значительном изменении нагрузки numad динамически перераспределит ресурсы.
Подробную информацию о numad можно найти на справочной странице man numad.