Хабрахабр

[Перевод] Преимущества и недостатки HugePages

Перевод статьи подготовлен для студентов курса «Администратор Linux».

Я встречал множество людей, которые обманываются перспективой того, что Hugepages волшебным образом повысят производительность. Ранее я рассказал о том, как проверить и включить использование Hugepages в Linux.
Эта статья будет полезна, только если у вас действительно есть, где использовать Hugepages. Тем не менее hugepaging является сложной темой, и при неправильном использовании он способен понизить производительность.

Проблема:
Необходимо проверить, включены ли HugePages в вашей системе.

Решение:
Оно довольно простое:

cat /sys/kernel/mm/transparent_hugepage/enabled

Вы получите что-то вроде этого:

always [madvise] never

Вы увидите список доступных опций (always, madvise, never), при этом текущая активная опция будет заключена в скобки (по умолчанию madvise).

madvise означает, что transparent hugepages включены только для областей памяти, которые явно запрашивают hugepages с помощью madvise(2).

Обычно это повышает производительность, но если у вас есть вариант использования, где множество процессов потребляет небольшое количество памяти, то общая нагрузка на память может резко возрасти. always означает, что transparent hugepages включены всегда и для всех процессов.

Чтобы узнать больше, обратитесь к документации ядра Linux. never означает, что transparent hugepages не будут включаться даже при запросе с помощью madvise.

Как изменить значение по умолчанию

Вариант 1: Напрямую изменить sysfs (после перезагрузки параметр вернется к значению по умолчанию):

echo always >/sys/kernel/mm/transparent_hugepage/enabled
echo madvise >/sys/kernel/mm/transparent_hugepage/enabled
echo never >/sys/kernel/mm/transparent_hugepage/enabled

Вариант 2: Измените системное значение по умолчанию, перекомпилировав ядро с измененной конфигурацией (этот вариант рекомендуется только если вы используете собственное ядро):

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

Обратите внимание, что мы говорим о 64-х разрядных x86 системах, работающих на Linux, и что я просто предполагаю, что система поддерживает transparent hugepages (так как не является недостатком то, что hugepages не подменяются), как это случается практически в любой современной среде Linux.

В ссылках ниже я прикреплю больше технического описания.

Виртуальная память

Если вы программист C++, вы знаете, что у объектов в памяти есть конкретные адреса (значения указателя).

Они представляют собой адреса в виртуальной памяти. Однако эти адреса необязательно отражают физические адреса в памяти (адреса в ОЗУ). Процессор имеет специальный модуль MMU (memory management unit), который помогает ядру сопоставлять виртуальную память с физическим местоположением.

Такой подход имеет множество преимуществ, но самые основные из них:

  • Производительность (по различным причинам);
  • Изоляция программ, то есть ни она из программ не может читать из памяти другой программы.

Что такое страницы?

Каждая отдельная страница указывает на определенную физическую память, она может указывать на область в оперативной памяти, а может на адрес, назначенный физическому устройству, например видеокарте. Виртуальная память поделена на страницы.

Ядро управляет физическим расположением каждой страницы. Большинство страниц, с которыми вы имеете дело, указывают либо на ОЗУ, либо подменяются (swap), то есть хранятся на жестком диске или SSD. Если осуществляется доступ к подмененной странице, ядро останавливает поток, который пытается получить доступ к памяти, считывает страницу с жесткого диска/SSD в оперативную память, а затем продолжает выполнение потока.

Размер нормальных страниц – 4096 байт. Этот процесс прозрачен для потока, то есть он не обязательно читает напрямую с жесткого диска/SSD. Размер Hugepages – 2 мегабайта.

Буфер ассоциативной трансляции (TLB)

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

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

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

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

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

Hugepages приходят на помощь

(Мы предполагаем, что программе все еще нужен тот же объем памяти). Итак, что мы можем сделать, чтобы избежать переполнения TLB?

Вместо 4096 байт, требующих всего одну запись в TLB, одна запись в TLB теперь может указывать на колоссальные 2 мегабайта. Вот тут-то и появляются Hugepages. Будем предполагать, что TLB имеет 512 записей, здесь без Hugepages мы можем сопоставить:

4096 b⋅512=2 MB

Тогда как с ними мы можем сопоставить:

2 MB⋅512=1 GB

Они могут повысить производительность без значительного приложения усилий. Именно поэтому Hugepages – это круто. Но здесь есть существенные оговорки.

Подмена Hugepages

Если физической памяти (ОЗУ) недостаточно, ядро переместит менее важные (реже используемые) страницы на жесткий диск, чтобы освободить часть ОЗУ для более важных страниц.
В принципе, то же самое касается и Hugepages. Ядро автоматически отслеживает частоту использования каждой страницы памяти. Однако ядро может менять местами только целые страницы, а не отдельные байты.

Предположим, у нас есть такая программа:

char* mymemory = malloc(2*1024*1024); // Возьмем это за одну Hugepage!
// Заполним mymemory какими-либо данными
// Сделаем много других вещей,
// которые приведут к подмене страницы mymemory
// ...
// Запросим доступ только к первому байту
putchar(mymemory[0]);

Что касается обычных страниц, с жесткого диска/SSD надо прочитать всего 4096 байт. В этом случае ядру нужно будет подменить (прочитать) целых 2 мегабайта информации с жесткого диска/SSD только для того чтобы вы прочитали один байт.

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

Тем не менее, вам нужно проверить это самостоятельно (а не на примере абстрактного ПО) и посмотреть, что будет работать быстрее. С другой стороны, если вам нужно получать доступ к большой части памяти последовательно, hugepages увеличат вашу производительность.

Аллокация в памяти

Допустим, вам нужно 30 байт памяти: Если вы пишете на С, вы знаете, что вы можете запросить сколь угодно малые (или почти сколь угодно большие) объемы памяти из кучи с помощью malloc().

char* mymemory = malloc(30);

Но на самом деле malloc () — это просто функция C, которая вызывает изнутри функции brk и sbrk для запроса или освобождения памяти из операционной системы. Программисту может показаться, что вы “запрашиваете” 30 байт памяти из операционной системы и возвращаете указатель на некоторую виртуальную память.

malloc() реализует довольно сложные алгоритмы для повторного использования освобожденной памяти. Однако, запрашивать больше и больше памяти для каждой аллокации неэффективно; наиболее вероятно, что какой-либо сегмент памяти уже был освобожден (free()), и мы можем повторно его использовать.

А потому, что вызов free() не означает, что память обязательно возвращается сразу же операционной системе. При этом для вас все происходит незаметно, так почему это должно вас волновать?

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

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

Выборочное применение hugepages

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

К счастью, вы можете использовать madvise(), чтобы включить hugepaging только для тех областей памяти, где это будет полезно.

Для начала, проверьте, что hugepages работают в режиме madvise(), с помощью инструкции в начале статьи.

Затем, используйте madvise(), чтобы указать ядру, где именно использовать hugepages.

#include <sys/mman.h>
// Аллоцируйте большое количество памяти, которую будете использовать
size_t size = 256*1024*1024;
char* mymemory = malloc(size);
// Просто включите hugepages…
madvise(mymemory, size, MADV_HUGEPAGE);
// … и задайте следующее
madvise(mymemory, size, MADV_HUGEPAGE | MADV_SEQUENTIAL)

Это не означает, что ядро будет автоматически использовать hugepages для заданной памяти. Обратите внимание, что этот метод — просто рекомендации ядру по управлению памятью.

Поэтому, если вы намереваетесь действительно хорошо разобраться в ней, подготовьтесь к чтению и тестированию в течение нескольких недель, прежде чем рассчитывать на хоть какой-то положительный результат. Обратитесь к документации (manpage) madvise, чтобы узнать больше об управлении памятью и madvise(), у этой темы невероятно крутая кривая обучения.

Что почитать?

Напишите в комментариях! Есть вопрос?

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть