Важливість розуміння когерентності кешу для розробників

Вступ

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

Когарентність кешу: більше, ніж просто питання апаратного забезпечення

Багато принципів, отриманих вивченням взаємопов'язаності кешу, мають пряме застосування у сферах розробки програмного забезпечення, таких як архітектура розподілених систем та рівні ізоляції баз даних. Розуміння, як узгодженість реалізована у апаратних кешах, може покращити розуміння концепцій eventual and strong consistency, особливо як вони відносяться до синхронізації даних в розподілених системах.

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

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

Протоколи взаємопов'язаності кешу. Огляд

Апаратні кеші на сучасних процесорах x86 синхронізуються за допомогою складних протоколів, що забезпечують узгодженість між всіма потоками. Один з широко використовуваних протоколів - це протокол MESI (Modified, Exclusive, Shared, Invalid). Стан даних у кеші може перебувати у будь-якому з цих чотирьох станів.

Modified (M): Дані були змінені і відрізняються від основної пам'яті. Exclusive (E): Дані не були змінені і синхронізовані з основною пам'яттю. Shared (S): Дані не були змінені і синхронізовані з іншими даними. Invalid (I): Дані застарілі і не повинні використовуватися.

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

Більш детальний опис роботи протоколу MESI

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

Припустимо, що потік на ядрі-1 бажає записати дані за адресою 0xabcd. Якщо кеш L1-1 вже містить ці дані, то відбувається "попадання в кеш" (cache hit), і запис відбувається безпосередньо. Однак, якщо дані відсутні в L1-1 або знаходяться в стані Shared, кеш L1-1 надсилає запит на отримання власності (Request-For-Ownership) до кешу L2. Це означає, що кеш L2 отримує ексклюзивний доступ до цих даних, і всі інші копії даних в інших кешах стають недійсними.

Кеш L2 відповідає за координацію взаємодії між кешами L1, забезпечуючи когерентність даних і відстежуючи, які кеші мають дійсні копії даних.

Схожий процес відбувається при зчитуванні даних. Якщо дані не присутні в кеші L1, відбувається "промах кешу" (cache miss), і система здійснює пошук даних у кеші L2 або, у разі необхідності, в глобальній пам'яті. Кеш L2 допомагає керувати когерентністю даних між кешами L1.

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


  • "L1" вказує на тип кешу - кеш першого рівня. Процесори часто використовують кілька рівнів кешу, зазвичай відмічені як L1, L2, і т.д., де L1 - це найшвидший і найближчий до ядра кеш, а L2, L3 і так далі є дещо повільнішими і “віддаленими“.

  • "-1" вказує на конкретне ядро процесора. У багатоядерних системах кожне ядро має власний кеш L1, тому "-1" позначає кеш L1 першого ядра. Для інших ядер це могло б бути L1-2, L1-3, L1-4 і так далі, в залежності від номера ядра.

Волатильні змінні та регістри

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

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


  1. https://docs.oracle.com/javase/tutorial/essential/concurrency

  2. https://stackoverflow.com/questions/tagged/concurrency

  3. https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html

  4. https://docs.oracle.com/javase/specs/jls/se20/jls20.pdf, Див. розділ 17.4, "Memory Model" для подробиць про багатопотоковість в Java.

  5. https://software.rajivprab.com/2018/04/29/myths-programmers-believe-about-cpu-caches/ — В основному, це переклад цієї статті, з доповненням зі вказаних ресурсів

  6. https://www.youtube.com/watch?v=r_ZE1XVT8Ao (картинка з цього відео)

Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Oleksandr Klymenko
Oleksandr Klymenko@overpathz

Java Software Engineer

3.3KПрочитань
1Автори
62Читачі
На Друкарні з 19 квітня

Більше від автора

  • Хешування паролів: використання солі та bcrypt

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

    Теми цього довгочиту:

    Security
  • Java. Повний огляд мережевих моделей. Socket API, forking, non-blocking sockets, event-driven API

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

    Теми цього довгочиту:

    Java
  • Java. WebSocket. Spring WebSocket

    Сервер в свою чергу повертає відповідь із 101 статус кодом — що так, давай змінимо протокол між тобою і мною. Тепер будемо використовувати вебсокети. Потім, після з’єднання, я розсилаю всім клієнтам інфомацію про нового користуча (і собі також, не робив додаткових перевірок).

    Теми цього довгочиту:

    Java

Вам також сподобається

Коментарі (0)

Підтримайте автора першим.
Напишіть коментар!

Вам також сподобається