← Назад к вопросам

К какому адресу в памяти происходит обращение в первую очередь

1.7 Middle🔥 111 комментариев
#Язык C++#Умные указатели и управление памятью

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Иерархия памяти: первичное обращение

Вопрос касается иерархии памяти в современных компьютерах. При обращении к данным процессор проходит через несколько уровней памяти, и каждый уровень проверяется по очереди.

Порядок обращения к памяти

Процессор обращается к памяти в следующем порядке:

1. Регистры процессора (самые быстрые, ~0.5 нс)

int x = 5;  // x часто хранится в регистре
x = x + 1;  // Обращение к регистру (~0.5 нс)

2. L1 Cache (~1-2 нс, 32-64 КБ)

  • Разделён на I-Cache (инструкции) и D-Cache (данные)
  • Для каждого ядра отдельно
  • Самый быстрый кеш

3. L2 Cache (~4-7 нс, 256 КБ)

  • Для каждого ядра отдельно
  • Больше чем L1, но медленнее

4. L3 Cache (~12-30 нс, 8-20 МБ)

  • Общий для всех ядер процессора
  • Самый большой кеш на чипе

5. Main Memory (RAM) (~50-100 нс, ГБ)

  • Оперативная память
  • Значительно медленнее кешей

6. Disk Storage (миллионы нс, ТБ)

  • Жёсткий диск или SSD
  • Используется как виртуальная память

Механизм кеширования

Процессор всегда сначала проверяет регистры и кеши, и только если данные не найдены (cache miss), обращается к следующему уровню.

// Пример: Обращение к массиву
int arr[1000];
int sum = 0;

for (int i = 0; i < 1000; i++) {
    sum += arr[i];  // Обращение к памяти
}

Что происходит:

  1. Процессор загружает arr[0] в L1 кеш
  2. Вместе с arr[0] загружается кеш-строка (обычно 64 байта) — содержит несколько соседних элементов
  3. arr[1], arr[2], arr[3] и т.д. — уже в L1 кеше (cache hit!)
  4. Только когда нужны данные из соседней кеш-строки — происходит обращение к памяти

Иерархия в цифрах

Уровень          Время доступа    Размер      На уровень выше
─────────────────────────────────────────────────────────────────
Регистр          0.5 нс           5-10 КБ     ~2x
L1 Cache         1-2 нс           32-64 КБ    ~4x
L2 Cache         4-7 нс           256 КБ      ~3x
L3 Cache         12-30 нс         8-20 МБ     ~4x
RAM              50-100 нс        ~16 ГБ      ~100000x
Disk             5,000,000 нс     1-2 ТБ      ~100000x

Cache Miss vs Cache Hit

Cache Hit — данные найдены в кеше (очень быстро)

int arr[100];
for (int i = 0; i < 100; i++) {
    arr[i] = i;  // Последовательный доступ = cache hits
}
// Производительность: хорошая

Cache Miss — данные не в кеше, нужно обращаться к памяти (медленно)

int arr[10000];
for (int i = 0; i < 10000; i += 100) {  // Случайные прыжки
    arr[i] = i;  // Много cache misses
}
// Производительность: плохая

NUMA архитектура (многопроцессорные системы)

На многопроцессорных системах с NUMA (Non-Uniform Memory Access) может быть разное время доступа:

Процессор 0  →  Локальная память (50 нс)  ✓ Быстро
Процессор 0  →  Память у Процессора 1 (200 нс)  ✗ Медленно

Оптимизация для кеша

1. Spatial Locality — обращайся к соседним адресам

// ✓ Хорошо: последовательный доступ
for (int i = 0; i < 1000; i++) {
    data[i] = i;
}

// ✗ Плохо: разреженный доступ
for (int i = 0; i < 1000; i += 64) {
    data[i] = i;
}

2. Temporal Locality — переиспользуй данные, пока они в кеше

// ✓ Хорошо: повторное использование
int x = expensive_calculation();
use(x);
use(x);
use(x);

// ✗ Плохо: длинный промежуток
int x = expensive_calculation();
many_other_calculations();
use(x);  // Может вывалиться из кеша

3. Cache line alignment — выравнивай данные по размеру кеш-строки

// Cache line обычно 64 байта
struct alignas(64) HotData {
    int frequently_accessed;
    char padding[60];  // Остальное место в кеш-строке
};

HotData data;  // Гарантированно в одной кеш-строке

False Sharing проблема

Оперативная память размещается эффективнее, если разные потоки обращаются к разным кеш-строкам:

struct Bad {
    int counter_thread0;  // В одной кеш-строке
    int counter_thread1;  // В одной кеш-строке
};
// Если разные потоки пишут в разные переменные,
// кеш-строка будет постоянно синхронизироваться между ядрами

struct Good {
    int counter_thread0;
    char padding[56];  // Разные кеш-строки
    int counter_thread1;
};
// Каждый поток может работать независимо

Резюме

При обращении к памяти первичный порядок:

  1. Регистры (микросекунды ЦП)
  2. L1 Cache (64 байта)
  3. L2 Cache (256 КБ)
  4. L3 Cache (8-20 МБ)
  5. RAM (ГБ)
  6. Disk (ТБ)

Современный процессор может предварительно загружать (prefetch) данные из предыдущего уровня, поэтому хороший код, использующий spatial и temporal locality, работает значительно быстрее.

К какому адресу в памяти происходит обращение в первую очередь | PrepBro