По ту сторону NUMA API

15 Дек 2014

По ту сторону NUMA API

Интеграция контроллера оперативной памяти в состав микросхемы CPU была одним из самых закономерных и ожидаемых событий. Преимущества очевидны — микросхем на плате меньше, быстродействие выше. Все просто и предсказуемо. Только не для мультипроцессорных систем: ведь теперь каждый процессор располагает своим контроллером памяти.

Итак, при включении компьютера, после завершения процедуры BIOS POST, в результате инициализации картирующей логики, все многочисленные модули DIMM многопроцессорной платформы, сгруппированы по каналам и размещены в едином и огромном 64-битном физическом адресном пространстве. Каждый из контроллеров памяти получил свой адресный диапазон. После загрузки ОС, механизмы виртуальной памяти и страничной трансляции скрывают от прикладного программного обеспечения фрагментирование физических ресурсов. Казалось бы, система полностью прозрачна, и программисту не нужно задумываться над тем, к какому процессору подключена используемая память.

В чем проблема?

Нетрудно понять, что для каждого из процессоров, доступ к собственной памяти осуществляется быстрее, чем к памяти, обслуживаемой соседним процессором, так как во втором случае информация должна пройти по шинам межпроцессорной связи. Именно поэтому, такая архитектура получила название NUMA или Non-Uniform Memory Access, что в переводе означает система с неоднородным доступом к памяти.

NUMA API

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

Для выделения памяти предусмотрена функция VirtualAllocExNuma, которая одним из входных параметров принимает номер узла (фактически процессорного сокета или физического процессора), который будет работать с выделяемым диапазоном.

При выполнении каждого программного потока может быть вызвана функция SetThreadAffinityMask, задающая 64-битный вектор в котором каждому из 64 возможных логических процессоров соответствует 0 (если потоку не рекомендуется использовать этот процессор) или 1 (если потоку рекомендуется использовать этот процессор). Векторы должны быть сформированы таким образом, чтобы каждый из потоков использовал логические процессоры, входящие только в тот узел, с которым ассоциирован диапазон памяти, используемый этим потоком, ранее выделенный функцией VirtualAllocExNuma.

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

ACPI System Resource Affinity Table

Рассмотренные выше функции NUMA API обеспечивают взаимодействие операционной системы и NUMA-оптимизированного приложения. Опустимся на ступеньку ниже в иерархии программных интерфейсов и рассмотрим механизм передачи топологической информации от Firmware платформы к ОС. Для этого, в рамках интерфейса ACPI (Advanced Configuration and Power Interface) предусмотрена специальная таблица SRAT (System Resource Affinity Table или, по другой версии Static Resource Affinity Table). Платформа представляется в виде набора узлов или доменов (proximity domains).

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

С программной точки зрения, в каждый домен входит группа логических процессоров и группа диапазонов адресного пространства.


Рис.1Заголовок таблицы System Resources Affinity Table (SRAT)
 
Рис.2Элемент таблицы SRAT — Processor Affinity Structure, структура, декларирующая принадлежность заданного логического процессора к заданному домену. Логические процессоры адресуются идентификатором Local APIC ID, по таким же правилам, как при межпроцессорном взаимодействии посредством подсистемы APIC (Advanced Programmable Interrupt Controller)
 
Рис.3Элемент таблицы SRAT — Memory Affinity Structure, структура, декларирующая принадлежность заданного диапазона памяти к заданному домену


Пример

Рассмотрим содержимое ACPI-таблицы System Resource Affinity Table (SRAT) на типовой двухпроцессорной платформе, оснащенной 64 гигабайтами оперативной памяти. Для этого нам потребовалось написать небольшую утилиту dump.ACPI.SRAT, обеспечивающую доступ к структурам Firmware из среды Win64.
 



Рис.4Дамп ACPI-таблицы SRAT, полученный на типовой двухпроцессорной платформе с суммарным объемом оперативной памяти 64 гигабайта


Текстовый файл содержит шестнадцатеричный дамп ACPI-таблицы SRAT, а файл — результат анализа ее содержимого. Как и ожидалось, декларировано два домена, по количеству процессорных сокетов. Каждый из доменов содержит 16 логических процессоров. В адресации оперативной памяти наблюдается асимметрия, обусловленная стремлением разработчиков разместить memory-mapped I/O ресурсы периферийных устройств ниже границы 4GB, для того, чтобы сделать их доступными 32-битным операционным системам. Вряд ли кому-то придет в голову использовать такие системы на данной платформе, но традиция должна быть соблюдена. 32 гигабайта домена 0 доступны виде двух фрагментов (0-2 GB и 4-34 GB). 32 гигабайта домена 1 доступны в диапазоне 34-66 GB. «Дырка» в диапазоне 2-4GB необходима для доступа 32-битных операционных систем к memory-mapped I/O. Заметим, что 32-битные ОС не могут адресовать память второго процессора (домена 1), но могут его использовать, размещая код и данные в памяти, подключенной к первому процессору (домену 0).

Вместе с тем, 32-битные приложения, работающие в среде 64-битной ОС, лишены указанных неудобств, так как механизм трансляции страниц в режиме PAE (Physical Address Extension) позволяет отобразить любую страницу 32-битного виртуального адресного пространства приложения на любую страницу 64-битного физического адресного пространства платформы. То же самое справедливо для 32-битных гостевых ОС, запущенных с применением 64-битных средств аппаратной виртуализации.

Резюме

Увы, любое равноправие иллюзорно. И в жизни, и в обществе, и в мультипроцессорной системе. Неоднородность доступа к ресурсам в современной платформе требует специальной оптимизации программного обеспечения на уровне Firmware, ОС и приложений.

Впервые опубликовано в Intel IT Galaxy