Виконання одноразового ADT орієнтованого коду на сучасних процесорах


32

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

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

Я сподівався, що хтось може пролити трохи світла на те, як це правда (чи це ні?).

У коментарі до іншого повідомлення було зазначено, що абстрактні типи даних (ADT) мають відношення до цього, що змусило мене ще більше цікавитись, як ADT конкретно впливає на те, як процесор має справу з пам'яттю? Це, однак, вбік, і я просто зацікавлений у тому, як чистота мови обов'язково впливає на продуктивність процесора та його кеш-файлів тощо


2
в основному це корисно в багатопотоковому читанні, де читач може атомним чином зробити знімок і бути в безпеці, знаючи, що він не буде мутувати під час його читання
храповий фрік

@ratchetfreak Я розумію, що з точки зору програмування ваш код отримує більш високі гарантії безпеки, але моя цікавість стосується контролера пам'яті в процесорі і того, як така поведінка має значення для нього (або якщо це не так), як я чув, що претензії є забороненими про руку, повну, яка казала, що це більш ефективно для контролера пам'яті, і я не знаю деталей низького рівня, щоб сказати, чи це може бути правдою.
Джиммі Хоффа

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

1
Я також хотів би зазначити, що ефективність пам'яті не обов'язково повинна залежати від оптимізації компілятора при використанні незмінних структур. У цьому прикладі let a = [1,2,3] in let b = 0:a in (a, b, (-1):c)поділі знижують вимоги до пам'яті, але залежить від визначення (:)і , []а не компілятор. Я думаю? Не впевнений у цьому.

Відповіді:


28

ЦП (конкретно його контролер пам'яті) може скористатися тим, що пам'ять не мутується

Перевага полягає в тому, що цей факт рятує компілятор від використання інструкцій з мембрани, коли доступ до даних.

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

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


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

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

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

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

  1. Усі ядра припиняють обробку - щоб уникнути випадкового запису в кеш.
  2. Усі оновлення локальних кешів списуються у глобальний - щоб гарантувати, що глобальний кеш містить найсвіжіші дані. Це займає певний час.
  3. Оновлені дані списуються з глобального кеша в локальний - щоб локальні кеші містили останні дані. Це займає певний час.
  4. Усі сердечники відновлять виконання.

Розумієте, усі ядра нічого не роблять, коли дані копіюються туди-сюди між глобальними та локальними кешами . Це необхідно для забезпечення правильної синхронізації змінних даних (безпечно для потоків). Якщо є 4 ядра, усі 4 зупиняються і чекають синхронізації кешів. Якщо їх 8, всі 8 зупиняються. Якщо їх 16 ... ну, у вас 15 ядер, які точно нічого не роблять, чекаючи речі, необхідні для виконання на одному з них.

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

Для компілятора це, в свою чергу, означає, що не потрібно вставляти інструкцію з мембрани .

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


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



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