awesome-everything EN
↑ Обратно к восхождению

Производительность

Cache vs big-O: чтение кода и раскладки

Суть Читаешь реальные сниппеты — strided-цикл, AoS-структуру, padded-счётчик и pointer chain, предсказываешь поведение кэша и pipeline, выбираешь фикс с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Проблемы кэша диагностируют в коде и профиле, а не в big-O. Прочитай каждый сниппет, предскажи, куда реально уходит wall-clock time, и выбери фикс, к которому senior-инженер тянется первым.

Цель

Отработай петлю, которую ты гоняешь в каждом инциденте производительности: прочитал горячий путь, нашёл дефект раскладки или паттерна доступа, который подтвердит профайлер, и выбрал фикс с наибольшим рычагом до SIMD-интринсиков и флагов компилятора.

Сниппет 1 — strided-сумма

double sum = 0;
// матрица row-major: matrix[i][j] по смещению (i*N + j)*8 байт
for (int j = 0; j < N; j++)        // внешний: индекс столбца
    for (int i = 0; i < N; i++)    // внутренний: индекс строки
        sum += matrix[i][j];
Викторина

На матрице 10 000×10 000 double, почему этот цикл в ~9 раз медленнее, чем при перестановке двух циклов, и какой фикс?

Сниппет 2 — раскладка структуры

struct Particle { float x, y, z, vx, vy, vz; char name[40]; };  // 64 байта
Particle parts[1_000_000];

float total_x = 0;
for (int i = 0; i < 1_000_000; i++)
    total_x += parts[i].x;          // читается только x
Викторина

Цикл читает только `x`, но профиль показывает высокий L3 miss rate. В чём дефект раскладки и фикс?

Сниппет 3 — padded-счётчик

type paddedCounter struct {
    value uint64
    _     [56]byte   // паддинг до полной 64-байтной cache line
}
var counters [16]paddedCounter

func worker(idx int) {                     // одна горутина на idx
    atomic.AddUint64(&counters[idx].value, 1)
}
Викторина

Что покупает паддинг `[56]byte` и какой сбой он предотвращает?

Сниппет 4 — pointer chain

// idx[] хранит следующий индекс для посещения; следование за ним data-dependent
int i = start;
for (int step = 0; step < N; step++) {
    process(data[i]);
    i = idx[i];          // следующий адрес зависит от результата этой загрузки
}
Викторина

Даже когда `data` и `idx` влезают в L2, это работает намного медленнее последовательного сканирования тех же массивов. Почему и какой структурный фикс?

Итог

Каждый сниппет здесь — дефект в константном множителе, которого big-O не видит: column-порядок цикла на row-major хранении промахивается на каждом шаге (переупорядочь или tiling); AoS-структура тянет холодные поля в линию для цикла по одному полю (раздели на SoA); непадженные счётчики на поток пинг-понгуют cache lines (паддинг до 64 байт); а dependent pointer chain сериализует промахи и ломает и MLP, и prefetcher (предвычисли contiguous порядок). Читай паттерн доступа, назови причину, исправь раскладку — затем перепрофилируй для подтверждения.

Продолжить восхождение ↑Cache vs big-O: опровергни учебник
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.