Наскільки важливим є вирівнювання пам’яті? Це все-таки має значення?


15

З деякого часу я багато шукав і читав про вирівнювання пам’яті, як це працює і як ним користуватися. Найбільш актуальна стаття, яку я зараз знайшов, - це ця .

Але навіть з цим у мене все ще є деякі питання з цього приводу:

  1. Поза вбудованою системою ми часто маємо величезний фрагмент пам'яті на нашому комп’ютері, що робить управління пам'яттю набагато меншим критиком, я повністю розбираюся в оптимізації, але тепер, чи справді це може змінити значення, якщо порівняти ту саму програму з або без пам'яті її переставляти та вирівнювати?
  2. Чи мають вирівнювання пам’яті інші переваги? Я десь читав, що процесор краще / швидше працює з вирівняною пам’яттю, оскільки для цього потрібно менше інструкцій обробляти (якщо хтось із вас має посилання на статтю / орієнтир про це?), В такому випадку різниця дійсно значна? Чи є більше переваг, ніж у цих двох?
  3. У посиланні на статтю у розділі 5 автор говорить:

    Остерігайтеся: у C ++ класи, схожі на структури, можуть порушити це правило! (Будь вони чи ні, залежить від того, як реалізуються базові класи та функції віртуальних членів і залежить від компілятора.)

  4. У статті йдеться переважно про структури, але чи на цю потребу впливає і декларація локальних змінних?

    Чи маєте ви якесь уявлення про те, як точно вирівнювання пам'яті працює в C ++, оскільки, здається, є деякі відмінності?

Це попереднє запитання містить слово "вирівнювання", але воно не дає жодних відповідей на вищезазначені питання.


Компілятори C ++ більш схильні робити це (вставляйте прокладки там, де це потрібно або вигідно). За посиланням, яке ви згадали, перегляньте у розділі 12 "Інструменти", що можна використовувати.
rwong

Відповіді:


11

Так, і вирівнювання, і розташування ваших даних можуть мати велику різницю в продуктивності, не лише на кілька відсотків, але на кілька-багато сотень відсотків.

Візьміть цю петлю, дві інструкції мають значення, якщо ви виконаєте достатньо циклів.

.globl ASMDELAY
ASMDELAY:
    subs r0,r0,#1
    bne ASMDELAY
    bx lr

З кешем і без нього, а також з вирівнюванням з та без кешу, кидаючи передбачення гілок, і ви можете змінити виконання цих двох інструкцій на значну кількість (тикер таймера):

min      max      difference
00016DDE 003E025D 003C947F

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

Те ж саме з доступом до даних. Деякі архітектури скаржаться на нестандартний доступ (наприклад, виконуючи зчитування 32 біт за адресою 0x1001), надаючи помилку даних. Деякі з них ви можете відключити помилку і прийняти показник продуктивності. Інші, які дозволяють несогласувати доступ, ви просто отримуєте хіт продуктивності.

Іноді це "інструкції", але більшість часу це цикли годин / автобусів.

Подивіться на реалізацію memcpy в gcc для різних цілей. Скажімо, ви копіюєте структуру, що становить 0x43 байт, ви можете знайти реалізацію, яка копіює один байт, залишаючи 0x42, потім копіює 0x40 байт у великі ефективні фрагменти, а потім останній 0x2, який він може робити як два окремих байти, або як 16-бітну передачу. Вирівнювання та ціль приходять у гру, якщо адреси джерела та місця призначення однакові, наприклад, 0x1003 та 0x2003, то ви можете зробити один байт, потім 0x40 у великих фрагментах, то 0x2, але якщо один 0x1002, а інший 0x1003, то він отримує справжнє потворне і справжнє повільне.

Більшість часу це автобусні цикли. Або гірше кількість передач. Візьміть процесор із 64-бітовою шиною даних, як ARM, і виконайте передачу чотирьох слів (читання або запис, LDM або STM) за адресою 0x1004, тобто адреса, що відповідає слову, і цілком законна, але якщо шина 64 біт шириною, ймовірно, що одна інструкція перетвориться на три передачі, в цьому випадку 32-бітний на 0x1004, 64-бітний на 0x1008 та 32-бітний на 0x100A. Але якщо ви мали ту саму інструкцію, але за адресою 0x1008, вона могла б здійснити передачу чотирьох слів за адресою 0x1008. Кожна передача пов'язана з часом налаштування. Таким чином, різниця адрес від 0x1004 до 0x1008 сама по собі може бути в кілька разів швидшою, навіть / esp при використанні кешу, і всі це хіти кешу.

Якщо говорити, навіть якщо ви прочитаєте два слова, прочитані за адресою 0x1000 проти 0x0FFC, 0x0FFC з помилками кешу призведе до того, що два кеш-рядки зчитуються, де 0x1000 - це одна лінія кешу, ви маєте покарання у кеш-рядку, прочитаному в будь-якому випадку для випадкового доступу (читання більше даних, ніж використання), але потім подвоюється. Як вирівнюються ваші структури або ваші дані взагалі та частота доступу до цих даних тощо можуть спричинити обробку кешу.

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

Усі речі, які ми додали для покращення продуктивності, ширшої шини даних, трубопроводів, кеш-пам'ять, прогнозування гілок, декількох одиниць / шляхів виконання тощо. Найчастіше допоможуть, але всі вони мають слабкі місця, які можна використовувати навмисно або випадково. Компілятор або бібліотеки можуть зробити з цим дуже мало, якщо вас цікавить продуктивність, вам потрібно налаштувати один з найбільших факторів настройки - вирівнювання коду та даних, а не просто вирівнювання на 32, 64, 128, 256 бітові межі, але також там, де речі відносно один одного, ви хочете, щоб широко використовувані цикли або повторно використовувані дані не переходили в той самий спосіб кешу, кожен з них хоче власне. Компілятори можуть допомогти, наприклад, впорядкувати вказівки для супер скалярної архітектури, переупорядкувати інструкції, що відносно один одного не мають значення,

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


+1, якщо тільки для вашого останнього абзацу. Ширина смуги пам’яті є найважливішим питанням для тих, хто сьогодні намагається написати швидкий код, а не кількість інструкцій. А це означає, що оптимізація речей для зменшення пропусків кешу, що може бути здійснено шляхом зміни вирівнювання за багатьох обставин, надзвичайно важливо.
Жуль

Якщо ваш код і дані стають кешованими, і ви виконуєте достатню кількість циклів / циклів на цих даних, тоді кількість інструкцій і коли інструкції лежать в межах лінії вибору, де гілки приземляються в трубі відносно того, на що вони покладаються, мають значення. Але в драматичних та / або флеш-системах спочатку потрібно потурбуватися про подачу процесора.
old_timer

15

Так, вирівнювання пам'яті все ще має значення.

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

Хороша новина полягає в тому, що вам в основному не потрібно піклуватися. Практично будь-який компілятор майже для будь-якої мови виробляє машинний код, який відповідає вимогам вирівнювання цільової системи. Починати думати про це потрібно лише в тому випадку, якщо ви безпосередньо керуєте поданням даних у пам'яті, що не потрібно десь поблизу так часто, як це було раніше. Це цікаво знати, і абсолютно критично знати, якщо ви хочете зрозуміти використання пам'яті з різних створених вами структур, і як, можливо, переупорядкувати речі, щоб бути ефективнішими (уникаючи прокладки). Але якщо вам не потрібен такий тип контролю (а для більшості систем ви просто не працюєте), ви можете щасливо пройти всю кар’єру, не знаючи і не піклуючись про це.


1
Зокрема, ARM не підтримує позаблоковий доступ. І це ЦП майже все, що використовує мобільний.
Ян Худек

Також зауважте, що Linux емулює позаблоковий доступ за певну ціну виконання, але Windows (CE та Phone) не робить і спроба несогласованного доступу просто збіть програму.
Ян Худек

2
Хоча це здебільшого вірно, зауважте, що деякі платформи (включаючи x86) мають різні вимоги до вирівнювання залежно від того, які інструкції будуть використовуватися , що компілятору нелегко працювати самостійно, тому вам іноді потрібно прокласти панель, щоб переконатися в цьому деякі операції (наприклад, інструкції SSE, багато з яких потребують 16-байтного вирівнювання) можуть використовуватися для деяких операцій. Крім того, додавання додаткових накладок так, що два елементи, які часто використовуються разом, трапляються в одній лінії кешу (також 16 байт), можуть мати величезний вплив на продуктивність у деяких випадках, а також не автоматизуються.
Жуль

3

Так, це все одно має значення, і в деяких критичних алгоритмах ефективності не можна покладатися на компілятор.

Я перерахую лише кілька прикладів:

  1. З цієї відповіді :

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

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

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


1

Ми схильні уникати ситуацій, коли це має значення. Якщо це має значення, це має значення. Неузгоджені дані, як правило, трапляються, наприклад, при обробці бінарних даних, чого, здається, сьогодні уникають (люди дуже часто використовують XML або JSON).

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

Пояснення посилання на C ++: У C всі поля структури повинні зберігатися у порядку зростання пам'яті. Отже, якщо у вас є поля char / double / char і хочете, щоб усе було вирівняно, у вас буде один байт char, сім байтів невикористаних, вісім байтів подвійний, один байт char, сім байт невикористаних. У структурах C ++ це те ж саме для сумісності. Але для структур компілятор може переупорядкувати поля, тому у вас може бути один байт char, інший байт char, шість байтів невикористаний, 8 байт подвійний. Використовуючи 16 замість 24 байт. У C структурах розробники зазвичай уникають такої ситуації і мають поля в іншому порядку в першу чергу.


1
Незрівняні дані трапляються в пам'яті. Програми, які не мають належним чином упакованих структур даних, можуть зазнати значних штрафних санкцій за навіть начебто невпливове впорядкування значень. Наприклад, у lthreaded коді два значення в одній лінії кешу спричинять масивні стояки конвеєра, коли два потоки отримують доступ до них одночасно (звичайно, ігноруючи проблеми безпеки потоку).
greyfade

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

1
Я ніколи не бачив поля перезапису компілятора C. Я бачив багато вкладишів та вирівнювання між символами / точками, наприклад, хоча ..
PaulHK

1

Багато гарних моментів уже згадуються у вищезазначених відповідях. Просто додати навіть у невбудовані системи, які займаються пошуком / видобутком даних, продуктивність пам'яті та час доступу є настільки важливими, що крім вирівнювання збіркового коду написано для того ж.

Я також рекомендую прочитати: http://dewaele.org/~robbe/thesis/writing/references/what-every-programmer-should-know-about-memory.2007.pdf


1

Наскільки важливим є вирівнювання пам’яті? Це все-таки має значення?

Так. Ні. Це залежить.

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

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

Чи мають вирівнювання пам’яті інші переваги? Я десь читав, що процесор краще / швидше працює з вирівняною пам’яттю, оскільки для цього потрібно менше інструкцій обробляти (якщо хтось із вас має посилання на статтю / орієнтир про це?), В такому випадку різниця дійсно значна? Чи є більше переваг, ніж у цих двох?

Це може бути. Це потрібно (можливо) пам’ятати під час написання коду, але в більшості випадків це просто не має значення (тобто я все ж упорядковую змінні свого члена за слідом пам’яті та частотою доступу - що має полегшити кешування - але я це роблю для простота використання / читання та рефакторинг коду, а не для кешування).

Чи маєте ви якесь уявлення про те, як точно вирівнювання пам'яті працює в C ++, оскільки, здається, є деякі відмінності?

Я читав про це, коли виходили речі з алігнофом (C ++ 11?), З цього часу я не переймався (я зараз роблю в основному настільні програми та сервер розробки сервера).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.