Gymterview
junior

Что такое кэш процессора (L1, L2, L3)?

Кэш процессора — это сверхбыстрая память малого объёма, расположенная непосредственно на кристалле процессора, которая хранит копии часто используемых данных из оперативной памяти, чтобы процессору не приходилось каждый раз обращаться к более медленной RAM.

Аналогия: представьте, что вы пишете реферат. L1-кэш — это лист бумаги прямо перед вами с ключевыми цитатами. L2 — стопка книг на столе. L3 — полка над столом. RAM — это библиотека через дорогу. Чем дальше источник, тем дольше идти, но тем больше книг доступно.

Иерархия кэша

Уровень Объём (типично) Задержка (латентность) Описание
L1 32-128 КБ на ядро ~1-4 такта (~1 нс) Самый быстрый и маленький. Разделён на L1d (данные) и L1i (инструкции). Индивидуальный для каждого ядра
L2 256 КБ - 1 МБ на ядро ~4-14 тактов (~3-5 нс) Быстрый, средний объём. Обычно индивидуальный для каждого ядра
L3 8-64 МБ (общий) ~30-70 тактов (~10-20 нс) Самый большой, но медленнее. Общий для всех ядер процессора
RAM 8-128 ГБ ~200-300 тактов (~50-100 нс) Основная оперативная память, значительно медленнее кэша

Принцип работы кэша

  1. Процессор запрашивает данные по адресу.
  2. Сначала проверяется L1. Если данные найдены — это cache hit (попадание).
  3. Если нет (cache miss) — проверяется L2, затем L3, и только потом — RAM.
  4. При загрузке данных из RAM они кэшируются на всех уровнях.

Принцип локальности

Это фундаментальное свойство, на которое опирается вся концепция кэширования:

  • Временная локальность (temporal locality) — если данные были использованы, они, скорее всего, понадобятся снова в ближайшее время.
  • Пространственная локальность (spatial locality) — если данные по адресу N использованы, скоро понадобятся данные по адресам N+1, N+2 и т.д.

Кэш-линия (cache line)

Кэш-линия — минимальная единица данных, загружаемая в кэш. Обычно составляет 64 байта. Даже если процессору нужен всего 1 байт, загружается целая кэш-линия. Это оптимизация под пространственную локальность — соседние байты, скорее всего, тоже понадобятся.

Значение для Java-разработчика

  • Обход массива последовательно (array[0], array[1], ...) значительно быстрее, чем случайный доступ к узлам LinkedList, из-за пространственной локальности. Элементы массива лежат рядом в памяти и попадают в одну кэш-линию, а узлы LinkedList разбросаны по куче.
  • False sharing — ситуация, когда два потока модифицируют разные переменные, попавшие в одну кэш-линию. Это приводит к постоянной инвалидации кэша между ядрами (протокол когерентности MESI) и серьёзной деградации производительности. В Java можно использовать аннотацию @Contended (или ручной padding) для разнесения переменных по разным кэш-линиям.
Пример
// Пример false sharing: два потока инкрементируют соседние поля
class FalseSharingExample {
    volatile long value1; // оба поля попадают в одну кэш-линию (64 байта)
    volatile long value2;
}

// Решение с @Contended (требует -XX:-RestrictContended)
class FixedExample {
    @jdk.internal.misc.Contended
    volatile long value1;
    @jdk.internal.misc.Contended
    volatile long value2;
}

Вывод

Кэш процессора — многоуровневая иерархия (L1, L2, L3), которая компенсирует разрыв в скорости между CPU и RAM. Эффективность кэша зависит от локальности доступа к данным. Для Java-разработчика это напрямую влияет на выбор структур данных (массив vs связный список) и на проектирование многопоточного кода (false sharing).

На собеседовании: вопрос уровня middle, часто задаётся в контексте производительности. Покажите, что понимаете, почему ArrayList быстрее LinkedList при итерации (кэш-локальность), и что такое false sharing. Если знаете аннотацию @Contended — это большой плюс.