Що повинен знати кожен програміст про пам'ять?


164

Мені цікаво, наскільки все ще діє Ульріха Дреппера, що повинен знати кожен програміст про пам'ять з 2007 року. Також я не зміг знайти більш нову версію, ніж 1.0 або errata.


1
хтось знає, чи можу я десь завантажити цю статтю у форматі mobi, щоб я міг її легко прочитати на запалі? "pdf" дуже важко читати через проблеми зі збільшенням / форматуванням
javapowered

1
Це не мобі, але LWN розробив папір як набір статей, які легше читати на телефоні / планшеті. Перший - на lwn.net/Articles/250967
Натан

Відповіді:


111

Наскільки я пам’ятаю, вміст Drepper описує основні поняття щодо пам’яті: як працює кеш процесора, що таке фізична та віртуальна пам’ять та як ядро ​​Linux обробляє цей зоопарк. Можливо, у деяких прикладах є застарілі посилання API, але це не має значення; це не вплине на актуальність основних понять.

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


3
Так, я дійсно не розумію, чому програмісту потрібно знати, як SRAM і DRAM працюють на аналоговому рівні - це не дуже допоможе при написанні програм. А людям, яким справді потрібні ці знання, краще проводити час, читаючи посібники про деталі про фактичні терміни тощо. Але людям, які цікавляться речами HW про низький рівень? Можливо, не корисно, але принаймні розважально.
Voo

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

144

Посібник у форматі PDF знаходиться за посиланням https://www.akkadia.org/drepper/cpumemory.pdf .

Він все ще відмінний і дуже рекомендується (як я вважаю, і я думаю, що інші фахівці з налаштування продуктивності). Було б здорово, якби Ульріх (або хто-небудь інший) написав оновлення 2017 року, але це було б багато роботи (наприклад, повторний запуск орієнтирів). Дивіться також інші налаштування продуктивності x86 та посилання на оптимізацію SSE / asm (та C / C ++) у програмі тег вікі . (Стаття Ульріха не відрізняється від x86, але більшість (усіх) його орієнтирів розміщені на апаратному забезпеченні x86.)

Деталі апаратного забезпечення низького рівня про те, як працюють DRAM та кеші, все ще застосовуються . DDR4 використовує ті самі команди, що й описані для DDR1 / DDR2 (пакет читання / запис). Удосконалення DDR3 / 4 не є принциповими змінами. AFAIK, усі незалежні від арки речі все ще застосовуються, наприклад, до AArch64 / ARM32.

Дивіться також розділ " Затримка зв'язаних платформ" у цій відповіді, щоб отримати важливі деталі про вплив затримки пам'яті / L3 на однопоточну пропускну здатність: bandwidth <= max_concurrency / latencyі це фактично основне вузьке місце для однопотокової смуги пропускання на сучасному багатоядерному процесорі, як Xeon . Але чотирьохядерний робочий стіл Skylake може наблизитися до збільшення пропускної здатності DRAM за допомогою однієї нитки. Це посилання містить дуже гарну інформацію про магазини NT порівняно з звичайними магазинами на x86. Чому Skylake настільки кращий, ніж Broadwell-E, для однопотокової пропускної здатності пам'яті? є підсумком.

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


(Зазвичай) Не використовуйте програмний попередній вибір

Одне головне, що змінилося, це те, що попередній вибір обладнання набагато кращий, ніж на Pentium 4, і він може розпізнати жорсткі шаблони доступу до досить великого кроку та декількох потоків одразу (наприклад, один вперед / назад на 4 к сторінки). Інструкція з оптимізації Intel описує деякі деталі попередньо встановлених HW в різних рівнях кешу для їх мікроархітектури сімейства Sandybridge. Ivybridge та пізніші версії мають апаратний попередній вибір, замість того, щоб чекати пропуску кешу на новій сторінці, щоб викликати швидкий старт. Я припускаю, що в посібнику з оптимізації AMD має подібні матеріали. Остерігайтеся, що посібник від Intel також сповнений старих порад, деякі з яких корисні лише для P4. Розділи, що стосуються Сендібріджу, звичайно точні для SnB, але, наприкладрозшарування мікроплавких вуоп змінилося в HSW, і керівництво не згадує про це .

Звичайна порада в цей час - видалити весь попередній вибір SW зі старого коду , і лише розглянути можливість його повернути, якщо профілювання показує пропуски кешу (і ви не насичуєте пропускну здатність пам'яті). Попередня вибір обох сторін наступного кроку двійкового пошуку все ще може допомогти. наприклад, як тільки ви вирішите, який елемент подивитись далі, попередньо встановіть елементи 1/4 та 3/4, щоб вони могли завантажуватися паралельно з серединою завантаження / перевірки.

Пропозиція використовувати окремий потік попереднього вибору (6.3.4) є абсолютно застарілим , я думаю, і він був хороший лише для Pentium 4. У P4 було гіперточення (2 логічних ядра, що розділяють одне фізичне ядро), але недостатньо кешу слідів (і / або ресурси, що виконуються поза замовленням) для отримання пропускної спроможності за допомогою двох повних обчислювальних потоків на одному ядрі. Але сучасні процесори (Сендібридж-сімейство та Ryzen) набагато бідніші, і вони повинні або запускати справжню нитку, або не використовувати гіпертокування (залиште інше логічне ядро ​​в режимі очікування, щоб соло-потік мав усі ресурси замість того, щоб розділити ROB).

Підготовка програмного забезпечення завжди була "крихкою" : правильна магічна настройка номерів для отримання прискорення залежить від деталей обладнання та, можливо, завантаження системи. Занадто рано, і його виселяють перед завантаженням попиту. Надто пізно, і це не допомагає. Ця стаття в блозі містить код + графіки для цікавого експерименту із використанням попереднього вибору SW на Haswell для попереднього вибору непослідовної частини проблеми. Див. Також Як правильно користуватися інструкціями щодо попереднього вибору? . Попереднє вилучення NT цікаве, але ще більш крихке, оскільки раннє виселення з L1 означає, що вам доведеться пройти весь шлях до L3 або DRAM, а не тільки до L2. Якщо вам потрібен кожен останній спад продуктивності, і ви можете налаштуватися на конкретну машину, SW попередній вибір варто переглянути для послідовного доступу, але цеМожливо, це все ще буде сповільненням, якщо вам доведеться виконати достатню кількість АЛУ роботи, під час наближення до вузького місця в пам'яті.


Розмір кеш-лінії все ще становить 64 байти. (Пропускна здатність L1D для читання / запису дуже велика. Сучасні процесори можуть робити 2 векторні навантаження на годинник + 1 векторний магазин, якщо все це потрапляє в L1D. Див. Як кеш може бути таким швидким? ). З AVX512, розмір рядка = ширина вектора, тож ви можете завантажити / зберегти весь рядок кешу в одній інструкції. Таким чином, кожен нерівне завантаження / зберігання перетинає межу кеш-лінії замість кожного іншого для 256b AVX1 / AVX2, що часто не сповільнює циклічне перетворення на масив, який не був у L1D.

Інструкції щодо нерівного завантаження мають нульовий штраф, якщо адреса вирівнюється під час виконання, але компілятори (особливо gcc) роблять кращий код під час автоматичної перевірки, якщо вони знають про будь-які гарантії вирівнювання. Насправді нестандартні операційні операції, як правило, швидкі, але розбиття сторінок все ж болить (набагато менше на Skylake, хоча; лише 11 зайвих циклів затримки проти 100, але все-таки пропускний показник).


Як передбачив Ульріх, в наші дні кожна мультирозетна система є NUMA: інтегровані контролери пам'яті є стандартними, тобто немає зовнішнього Northbridge. Але SMP більше не означає мульти сокет, оскільки багатоядерні процесори широко поширені. Процесорні процесори Intel від Nehalem до Skylake використовували великий включений кеш-пам'ять L3 як резервний стоп для когерентності між ядрами. Процесорні процесори AMD різні, але в деталях мені не так зрозуміло.

У Skylake-X (AVX512) більше немає включеного L3, але я думаю, що досі існує тег-каталог, який дозволяє перевіряти, що є в кеші де-небудь на чіпі (і якщо так, де), не фактично транслюючи снопок на всі ядра. На жаль, SKX використовує сітку, а не кільцеву шину , із загалом ще гіршими затримками, ніж попередні багатоядерні Xeons, на жаль.

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


6.4.2 Атомні операційні можливості : тест, що показує цикл повторної спроби CAS, як в 4 рази гірший, ніж арбітражний арбітраж lock add, ймовірно, все ще відображає випадок максимальної суперечки . Але в реальних багатопотокових програмах синхронізація зводиться до мінімуму (тому що це дорого), тому суперечка низька, і цикл CAS-повторної спроби, як правило, досягає успіху без необхідності повторювати.

C ++ 11 std::atomic fetch_addбуде компілюватися до lock add(або lock xaddякщо використовується повернене значення), але алгоритм, що використовує CAS, щоб зробити те, що неможливо зробити за допомогою lockінструкції ed, зазвичай не є катастрофою. Використовуйте C ++ 11std::atomic або C11 stdatomicзамість застарілих __syncвбудованих програм GCC або новіших __atomicвбудованих програм, якщо ви не хочете змішувати атомний та неатомний доступ до одного місця ...

8.1 DWCAS ( cmpxchg16b) : Ви можете примусити gcc випромінювати його, але якщо вам потрібні ефективні навантаження лише однієї половини об'єкта, вам потрібні некрасиві unionхаки: Як я можу реалізувати лічильник ABA з c ++ 11 CAS? . (Не плутайте DWCAS з DCAS з двох окремих місць пам’яті . Безблокова атомна емуляція DCAS неможлива з DWCAS, але транзакційна пам'ять (як x86 TSX) робить це можливим.)

8.2.4 транзакційна пам'ять : Після пари помилкових запусків (випущених та відключених оновленням мікрокоду через рідко викликану помилку), Intel має працюючу транзакційну пам’ять у пізній моделі Broadwell та всіх процесорах Skylake. Дизайн - це все те, що описав Девід Кантер для Haswell . Існує спосіб блокування блокування для прискорення коду, який використовує (і може повернутися до) звичайний замок (особливо з одним блокуванням для всіх елементів контейнера, тому кілька потоків в одному критичному розділі часто не стикаються. ) або написати код, який безпосередньо знає про транзакції.


7.5. Hugepages : анонімні прозорі величезні сторінки добре працюють у Linux, не використовуючи hugetlbfs вручну. Зробіть асигнування> = 2MiB з вирівнюванням 2MiB (наприклад posix_memalign, або,aligned_alloc що не примушує дурного вимоги ISO C ++ 17 вийти з ладу, коли size % alignment != 0).

Анонімний розподіл, вирівняний 2MiB, використовує величезні сторінки за замовчуванням. Деякі навантаження (наприклад, які продовжують використовувати великі асигнування деякий час після їх створення) можуть отримати користь від того,
echo always >/sys/kernel/mm/transparent_hugepage/defragщоб ядро ​​дефрагментувати фізичну пам'ять, коли це потрібно, замість того, щоб повертатися до 4-х сторінок. (Див . Документи ядра ). Крім того, використовуйте madvise(MADV_HUGEPAGE)після великих розмірів (бажано, щоб вони були з вирівнюванням 2MiB).


Додаток B: Oprofile : Linux perfздебільшого витіснив oprofile. Для детальних подій, характерних для певних мікроархітектур, використовуйте ocperf.pyобгортку . напр

ocperf.py stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,\
branches,branch-misses,instructions,uops_issued.any,\
uops_executed.thread,idq_uops_not_delivered.core -r2 ./a.out

Деякі приклади його використання див. У розділі Чи може MOV x86 дійсно бути "вільним"? Чому я взагалі не можу це відтворити? .


3
Дуже повчальна відповідь та покажчики! Це явно заслуговує на більше голосів!
клаф

@ Peter Cordes Чи є інші посібники / документи, які ви рекомендуєте прочитати? Я не високопродуктивний програміст, але хотів би дізнатися більше про це і, сподіваюся, підібрати практику, яку я можу включити до свого щоденного програмування.
користувач3927312

4
@ user3927312: agner.org/optimize - це одне з найкращих і узгоджених посібників щодо матеріалів низького рівня конкретно для x86, але деякі загальні ідеї стосуються інших ISA. Як і посібники з Asm, Agner має оптимізуючий C ++ PDF. Інші посилання на ефективність / архітектуру процесора див . У розділі stackoverflow.com/tags/x86/info . Я також писав про оптимізацію C ++, допомагаючи компілятору зробити кращу тривогу для критичних циклів, коли варто ознайомитись з результатом роботи asm компілятора: код C ++ для тестування гіпотези Collatz швидше, ніж рукописний асм?
Пітер Кордес

74

З мого швидкого огляду це виглядає досить точно. Єдине, що слід помітити, це частина різниці між "інтегрованими" та "зовнішніми" контролерами пам'яті. З моменту випуску ліній i7-процесорів Intel все інтегровано, а AMD використовує інтегровані контролери пам'яті з моменту вперше випуску мікросхем AMD64.

Оскільки ця стаття була написана, не ціла партія змінилася, швидкість зросла вище, контролери пам'яті стали набагато розумнішими (i7 буде затримувати запис в оперативну пам'ять, поки не відчує себе, як здійснити зміни), але не ціла партія змінилася . Принаймні, ні в якому разі не піклується розробник програмного забезпечення.


5
Я хотів би прийняти вас обох. Але я відхилив вашу посаду.
Фраместер

5
Напевно, найважливіша зміна, що стосується розробників SW, полягає в тому, що теми попереднього вибору - це погана ідея. Процесори достатньо потужні для запуску 2 повних потоків з гіперточенням і мають набагато кращий попередній вибір HW. Загальний попередній вибір SW взагалі набагато менш важливий, особливо для послідовного доступу. Дивіться мою відповідь.
Пітер Кордес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.