Переваги та недоліки HugePages

Переваги та недоліки HugePages

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

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

Частина 1: перевіряємо, що hugepages включені в Linux (оригінал тут)

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

Рішення:
Воно досить просте:

cat /sys/kernel/mm/transparent_hugepage/enabled

Ви отримаєте щось на кшталт цього:

always [madvise] never

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

madvise означає, що transparent hugepages включені тільки для областей пам'яті, які явно запитують hugepages за допомогою madvise(2).

завжди означає, що transparent hugepages завжди включені і для всіх процесів. Зазвичай це підвищує продуктивність, але якщо у вас є варіант використання, де безліч процесів споживає невелику кількість пам'яті, загальне навантаження на пам'ять може різко зрости.

ніколи означає, що transparent hugepages не будуть включатись навіть при запиті за допомогою madvise. Щоб дізнатися більше, зверніться до документації ядра Linux.

Як змінити значення за замовчуванням

Варіант 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: Змініть системне значення за умовчанням, перекомпілювавши ядро ​​зі зміненою конфігурацією (цей варіант рекомендується лише якщо ви використовуєте власне ядро):

  • Щоб встановити always за умовчанням, використовуйте:
    CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y
    # Comment out CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y
  • Щоб встановити madvise за замовчуванням, використовуйте:
    CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y
    # Comment out CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y

Частина 2: Переваги та недоліки HugePages

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

Зверніть увагу, що ми говоримо про 64-розрядні x86 системи, що працюють на Linux, і що я просто припускаю, що система підтримує transparent hugepages (оскільки не є недоліком те, що hugepages не підмінюються), як це трапляється практично в будь-якій сучасній середовищі Linux.

У нижченаведених посиланнях я прикріплю більше технічного опису.

Віртуальна пам'ять

Якщо ви програміст C++, ви знаєте, що об'єкти в пам'яті мають конкретні адреси (значення покажчика).

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

Такий підхід має безліч переваг, але найголовніші з них:

  • Продуктивність (з різних причин);
  • Ізоляція програм, тобто жодна із програм не може читати з пам'яті іншої програми.

Що таке сторінки?

Віртуальна пам'ять поділена на сторінки. Кожна окрема сторінка вказує на певну фізичну пам'ять, вона може вказувати на область оперативної пам'яті, а може на адресу, призначену фізичному пристрою, наприклад відеокарті.

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

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

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

Коли програма звертається до певної сторінки пам'яті, центральний процесор повинен знати, з якої фізичної сторінки зчитувати дані (тобто мати віртуальну картку адрес).

В ядрі є структура даних (таблиця сторінок), яка містить всю інформацію про сторінки, що використовуються. За допомогою цієї структури даних можна зіставити віртуальну адресу із фізичною адресою.

Однак таблиця сторінок досить складна і працює повільно, тому ми просто не можемо щоразу аналізувати всю структуру даних, коли процес звертається до пам'яті.

На щастя у нашому процесорі є TLB, який кешує зіставлення віртуальних та фізичних адрес. Це означає, що незважаючи на те, що нам потрібно проаналізувати таблицю сторінок при першій спробі отримати доступ, всі наступні звернення до сторінки можуть бути оброблені в TLB, що забезпечує швидку роботу.

Оскільки він реалізований як фізичний пристрій (що робить його в першу чергу швидким), його ємність обмежена. Тому, якщо ви захочете отримати доступ до більшої кількості сторінок, TLB не зможе зберігати зіставлення для всіх них, внаслідок чого ваша програма працюватиме набагато повільніше.

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

Отже, що ми можемо зробити, щоб уникнути переповнення TLB? (Ми припускаємо, що програмі все ще потрібний той самий обсяг пам'яті).

Ось тут і з'являються Hugepages. Замість 4096 байт, що вимагають лише один запис у TLB, один запис у TLB тепер може вказувати на колосальні 2 мегабайти. Припускатимемо, що 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]); 

У цьому випадку ядру потрібно буде підмінити (прочитати) цілих 2 мегабайти інформації з жорсткого диска/SSD тільки для того, щоб ви прочитали один байт. Що стосується звичайних сторінок, з жорсткого диска/SSD треба прочитати лише 4096 байт.

Тому, якщо hugepage підміняється, її читання відбувається швидше, тільки якщо вам потрібно отримати доступ до всієї сторінки. Це означає, що якщо ви намагаєтеся отримати доступ випадковим чином до різних частин пам'яті і просто зчитуєте пару кілобайт, вам слід використовувати звичайні сторінки і більше не турбуватися.

З іншого боку, якщо вам потрібно отримувати доступ до великої частини пам'яті послідовно, hugepages збільшать вашу продуктивність. Тим не менш, вам потрібно перевірити це самостійно (а не на прикладі абстрактного програмного забезпечення) і подивитися, що працюватиме швидше.

Алокація у пам'яті

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

char* mymemory = malloc(30);

Програмістові може здатися, що ви “запитуєте” 30 байт пам'яті з операційної системи та повертаєте вказівник на деяку віртуальну пам'ять. Але насправді malloc () - це просто функція C, яка викликає зсередини функції brk та sbrk для запиту або звільнення пам'яті операційної системи.

Однак, запитувати більше і більше пам'яті для кожної аллокації є неефективним; найімовірніше, що якийсь сегмент пам'яті вже було звільнено (free()), і ми можемо повторно використовувати його. malloc() реалізує досить складні алгоритми повторного використання звільненої пам'яті.

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

Існує таке поняття, як фрагментація пам'яті. У крайніх випадках є сегменти купи, де використовується лише кілька байтів, у той час, як усе, що знаходиться між ними, було звільнено (free()).

Зауважте, що фрагментація пам'яті є неймовірно складною темою, і навіть незначні зміни в програмі можуть значно вплинути на неї. У більшості випадків програми не викликають значної фрагментації пам'яті, але ви повинні мати на увазі, що якщо з фрагментацією в деякій області купи виникла проблема, великасторінка може тільки посилити ситуацію.

Вибіркове застосування 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()у цієї теми неймовірно крута крива навчання. Тому, якщо ви маєте намір справді добре розібратися в ній, підготуйтеся до читання та тестування протягом кількох тижнів, перш ніж розраховувати на хоч якийсь позитивний результат.

Що почитати?

Є питання? Напишіть у коментарях!

Джерело: habr.com

Додати коментар або відгук