Як працюють кеш-лінії?


169

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

Моє запитання:

Уявіть, що вам потрібно прочитати один байт з пам'яті, який 64 байти буде внесено в кеш?

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

Що це таке?


22
Прочитайте це: Що повинен знати кожен програміст про пам'ять . Потім прочитайте ще раз. Краще (pdf) джерело тут .
andersoj

Відповіді:


129

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

Сучасні модулі пам'яті ПК переносять 64 біти (8 байт) одночасно, у вісім передач , тож одна команда запускає читання або запис повного рядка кешу з пам'яті. (Розмір передачі пакетної передачі SDRAM / DDR1 / 2/3/4 може налаштовуватися до 64B; процесори виберуть розмір передачі пакетної передачі відповідно до розміру рядка кешу, але 64B є загальним)

Як правило, якщо процесор не може передбачити доступ до пам'яті (і попередньо вилучити її), процес пошуку може тривати ~ 90 наносекунд або ~ 250 тактових циклів (від процесора, знаючи адресу до ЦП, що отримує дані).

На противагу цьому, потрапляння в кеш L1 має затримку для завантаження в 3 або 4 цикли, а перезавантаження магазину має затримку для переадресації магазину в 4 або 5 циклів на сучасних процесорах x86. Речі схожі на інших архітектурах.

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


1
Ця відповідь абсолютно не має сенсу. Що не має пропускної здатності 64-бітової пам’яті (що також неправильно в цьому відношенні) до 64-байтового (!)? Також 10 - 30 нс також абсолютно помиляються, якщо вдарити Барана. Це може бути вірно для кешу L3 або L2, але не для оперативної пам'яті, де вона більше схожа на 90ns. То, що ви маєте на увазі, є час вибуху - час доступу до наступного чотирьох слова у режимі зриву (що насправді є правильною відповіддю)
Мартін Керстен

5
@MartinKersten: Один канал DDR1 / 2/3/4 SDRAM використовує 64-бітну шину даних. Бурхлива передача цілого рядка кешу займає вісім передач по 8В кожен, і це те, що відбувається насправді. Можливо, все-таки правильним є те, що процес оптимізований за допомогою передачі 8-вирівнюваного фрагмента, що містить бажаний байт, тобто запустити імпульс (і обернути його, якщо це був не перший 8В розміру передачі передачі). Сучасні процесори з багаторівневими кешами, мабуть, більше цього не роблять, оскільки це означатиме раннє відновлення першого блоку (-ів) вибуху до кешу L1.
Пітер Кордес

2
Haswell має шлях 64B між кешем L2 та L1D (тобто повна ширина кеш-лінії), тому перенесення 8B, що містить запитуваний байт, призведе до неефективного використання цієї шини. @Martin також вірно стосується часу доступу для завантаження, яке повинно пройти до основної пам'яті.
Пітер Кордес

3
Добре запитання про те, чи дані одразу піднімаються вгору за ієрархією пам'яті, або ж L3 чекає повного рядка з пам'яті перед тим, як почати надсилати їх до L2. Існують буфери передачі між різними рівнями кешу, і кожен непогашений пропускає претензії один. Отже ( загальна здогадка ), ймовірно, L3 ставить байти від контролера пам'яті у власний буфер прийому одночасно з тим, як поміщає їх у відповідний буфер завантаження для кешу L2, який цього хотів. Коли рядок повністю передається з пам'яті, L3 повідомляє L2 про те, що лінія готова, і копіює її у свій власний масив.
Пітер Кордес

2
@Martin: Я вирішив продовжити та відредагувати цю відповідь. Я думаю, це зараз точніше і все-таки просто. Майбутні читачі: дивіться також питання Mike76 та мою відповідь: stackoverflow.com/questions/39182060/…
Пітер Кордес

22

Якщо рядки кеша шириною 64 байти, то вони відповідають блокам пам'яті, які починаються з адрес, що діляться на 64. Найменш значущі 6 біт будь-якої адреси є зміщенням у кеш-рядку.

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

Хоча це робиться апаратно, ми можемо показати обчислення, використовуючи деякі еталонні визначення макросу C:

#define CACHE_BLOCK_BITS 6
#define CACHE_BLOCK_SIZE (1U << CACHE_BLOCK_BITS)  /* 64 */
#define CACHE_BLOCK_MASK (CACHE_BLOCK_SIZE - 1)    /* 63, 0x3F */

/* Which byte offset in its cache block does this address reference? */
#define CACHE_BLOCK_OFFSET(ADDR) ((ADDR) & CACHE_BLOCK_MASK)

/* Address of 64 byte block brought into the cache when ADDR accessed */
#define CACHE_BLOCK_ALIGNED_ADDR(ADDR) ((ADDR) & ~CACHE_BLOCK_MASK)

1
Мені важко це зрозуміти. Я знаю, що через 2 роки, але ви можете надати приклад коду для цього? один-два рядки.
Нік

1
@ Nick Причина, по якій цей метод працює, криється в системі двійкових чисел. Будь-яка потужність 2 має лише один біт, а всі біти очищені, тому для 64-х ви 0b1000000помітили, що останні 6 цифр - нулі, тому навіть коли у вас є якесь число з будь-якого з цих 6 наборів (які представляють число % 64), очистивши їх, ви отримаєте найближчу 64-байтну вирівняну пам'ять.
legends2k

21

Перш за все, доступ до основної пам'яті дуже дорогий. В даний час процесор 2 ГГц (найповільніший один раз) має 2G тиків (циклів) в секунду. ЦП (віртуальне ядро ​​сьогодні) може отримати значення з його регістрів один раз за галочку. Оскільки віртуальне ядро ​​складається з декількох одиниць обробки (ALU - арифметична логічна одиниця, FPU тощо), воно може фактично обробляти певні інструкції, якщо це можливо.

Доступ до основної пам'яті коштує приблизно від 70 до 100с (DDR4 трохи швидше). Цей час, в основному, шукає кеш L1, L2 і L3 і, ніж натискає на пам'ять (відправляємо команду на контролер пам'яті, який надсилає її в банки пам'яті), чекаємо відповіді і робимо.

100ns означає близько 200 кліщів. Отже, якщо програма завжди пропускатиме кеші, до яких має доступ кожна пам'ять, процесор витрачає приблизно 99,5% свого часу (якщо вона лише зчитує пам'ять) в режимі очікування пам'яті.

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

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

Дати уявлення про терміни (джерело: витрати на доступ до кешів та пам'яті )

  • Кеш L1: від 1 до 2 с (2-4 цикли)
  • Кеш L2: 3ns до 5ns (6-10 циклів)
  • Кеш L3: від 12 до 20 секунд (24-40 циклів)
  • ОЗУ: 60 секунд (120 циклів)

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

Таким чином, кеш в основному значно прискорює доступ до пам'яті (60ns проти 1ns).

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

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

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

Отже, аналізуючи схему доступу до пам'яті, було очевидно, що дані читаються послідовно дуже часто. Існувала велика ймовірність того, що якщо програма прочитає значення в індексі i, програма також прочитає значення i + 1. Ця ймовірність трохи вище, ніж ймовірність того, що та сама програма також буде читати значення i + 2 тощо.

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

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

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

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

Ще одна проблема, яку вирішує кеш-лінія (окрім читання вперед та збереження / звільнення шести біт на адресній шині), полягає в тому, як організовано кеш-пам'ять. Наприклад, якщо кеш буде розділений на 8-байтних (64-бітових) блоків (комірок), потрібно зберегти адресу комірки пам’яті, для якої ця клітинка кешу містить разом із нею значення. Якщо адреса також буде 64-бітовою, це означає, що половина розміру кешу споживається адресою, що призводить до накладних витрат у 100%.

Оскільки лінія кешу становить 64 байт і процесор може використовувати 64 біт - 6 біт = 58 біт (не потрібно зберігати нульові біти занадто правильно), то ми можемо кешувати 64 байт або 512 біт з накладними витратами на 58 біт (11% накладних витрат). Насправді адреси, що зберігаються, навіть менші, ніж ці, але є інформація про стан (наприклад, правильність та точність кеш-пам’яті, брудна та її потрібно записати в операційному режимі тощо).

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

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

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

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


1
Кеш-асоціативний кеш використовує деякі біти адреси для вибору набору, тому теги можуть бути навіть коротшими, ніж ваш приклад. Звичайно, кеш також повинен відслідковувати, який тег йде з яким масивом даних у наборі, але зазвичай існує більше наборів, ніж способів всередині набору. (наприклад, 8-сторонній асоціативний кеш L1D з 32 кБ, з 64B рядками, в процесорах Intel x86: зміщення 6 біт, індекс 6 біт. Теги мають бути лише 48-12 біт шириною, оскільки x86-64 (поки що) має лише 48- бітові фізичні адреси. Як я впевнений, ви знаєте, це не випадковість, що низькі 12 біт - це зміщення сторінки, так що L1 може бути VIPT, не створюючи прізвища.)
Пітер Кордес

дивовижний бутон відповідей ... чи є десь кнопка "подобається"?
Едгард Ліма

@EdgardLima, а не кнопка оновлення?
Pacerier

6

Процесори можуть мати багаторівневі кеші (L1, L2, L3), і вони відрізняються за розміром і швидкістю.

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

Читайте про політику прогнозування гілок , кеш-пам'ять процесора та політику заміни .

Це непросте завдання. Якщо наприкінці дня все, що вам потрібно, - це тест на працездатність, ви можете використовувати такий інструмент, як Cachegrind . Однак, оскільки це симуляція, її результат може в якійсь мірі відрізнятися.


4

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


2
Я можу сказати напевно. Будь-яка розумна конструкція кешу матиме лінії розміром, які мають потужність 2, і вони природно вирівняні. (напр., 64В). Це не просто швидко і просто, це буквально безкоштовно: ви просто ігноруєте, наприклад, низькі 6 біт адреси. Кеші часто роблять різні речі з різним діапазоном адрес. (наприклад, кеш піклується про тег та індекс для виявлення звернення та пропуску, а потім лише зсув у рядку кеша для вставки / вилучення даних)
Peter Cordes
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.