Векторизация: думать матрицами вместо циклов

Самый важный урок курса: почему в MATLAB цикл — почти всегда признак, что вы пишете не на том языке.

Векторизация — переписывание поэлементных циклов как операций над целыми массивами.

Один и тот же расчёт двумя способами

Допустим, нужно возвести в квадрат миллион чисел. На привычный лад вы бы написали цикл. На языке MATLAB то же самое — одна строка без цикла. Сравните мышление.

% Цикл — так писать НЕ нужно
n = 1000000;
y = zeros(1, n);
for i = 1:n
    y(i) = i^2;
end

% Векторизованно — идиоматичный MATLAB
x = 1:n;
y = x.^2;

Векторизованная версия не просто короче — она в разы быстрее, потому что вся работа уходит в скомпилированное ядро, а не выполняется интерпретатором шаг за шагом.

Почему циклы медленные

MATLAB — интерпретируемый язык. Каждая итерация цикла — это отдельная команда, которую интерпретатор разбирает заново: проверяет типы, размеры, границы. Миллион итераций — миллион таких проверок. А x.^2 отдаёт весь массив в оптимизированную функцию, написанную на C, которая обрабатывает данные единым проходом, используя векторные инструкции процессора. Отсюда разница в десятки и сотни раз.

Векторизация условий

Циклы с if внутри тоже векторизуются — через логическую индексацию из прошлого раздела. Хотите обнулить отрицательные элементы? Не нужен цикл с проверкой каждого.

v = [-3 5 -1 8 -2];
v(v < 0) = 0;        % вместо цикла с if
% v = 0 5 0 8 0

Накопление без цикла

Суммы и произведения по массиву считают встроенные функции, а не ручной аккумулятор: sum, prod, cumsum (нарастающая сумма). Скалярное произведение двух векторов — это sum(a.*b) или просто a*b'.

a = [1 2 3 4];
total = sum(a)        % 10
running = cumsum(a)   % 1 3 6 10

Как работает под капотом

Векторизованная операция выигрывает по трём причинам сразу. Первая — нет накладных расходов интерпретатора на каждую итерацию. Вторая — данные лежат в памяти подряд, и процессор читает их кэш-дружественно. Третья — современные CPU умеют обрабатывать несколько чисел за одну инструкцию (SIMD), и матричные примитивы это используют. Цикл на уровне языка MATLAB не даёт ни одного из этих преимуществ. Именно поэтому «думать матрицами» — не стилистическая прихоть, а способ получить скорость.

Частые ошибки

  • Переносить привычку из C/Python и писать циклы там, где есть векторная операция.
  • Векторизовать, но забыть preallocation в редких оставшихся циклах — тогда они особенно медленны.
  • Усложнять читаемость ради «суперкороткой» строки: иногда понятный цикл лучше непостижимого однострочника. Баланс важен.

Когда цикл всё-таки нужен

Призыв «избегать циклов» не стоит понимать как «циклы запрещены». Есть задачи, которые принципиально последовательны: каждая итерация зависит от результата предыдущей. Итерационные численные методы (например, Ньютона), рекуррентные соотношения, моделирование шаг за шагом во времени — здесь цикл естественен и правилен. Векторизация бессильна там, где нет независимости между элементами. Зрелый подход — векторизовать всё, что независимо, и оставлять циклы только для подлинно последовательной логики. И даже тогда тело цикла стараются делать как можно «легче», вынося из него всё, что можно посчитать заранее.

Функции, которые заменяют циклы

В арсенале MATLAB есть семейство функций, специально созданных, чтобы избегать явных циклов. arrayfun применяет функцию к каждому элементу массива, cellfun — к каждой ячейке cell-массива, accumarray группирует и агрегирует значения по ключам. Логические any, all, find, кумулятивные cumsum, cumprod покрывают типичные «накопительные» циклы. Прежде чем писать цикл, полезно спросить себя: нет ли встроенной функции, которая делает ровно это? В девяти случаях из десяти она есть — и работает быстрее, и читается яснее. Этот рефлекс — «ищу векторную функцию вместо цикла» — и есть практическое выражение матричного мышления.

Итоги

  • Векторизация заменяет циклы операциями над массивами — главный навык MATLAB.
  • Скорость даёт скомпилированное ядро, кэш и SIMD, а не интерпретируемый цикл.
  • Условия и накопления тоже векторизуются — через маски, sum, cumsum.
Проверьте себя
1. Почему векторизованный код в MATLAB быстрее цикла?
AЦикл всегда содержит ошибку
BОперация уходит в скомпилированное ядро вместо пошагового интерпретатора
CВекторы занимают меньше памяти
DMATLAB не умеет циклы
2. Как векторизованно обнулить отрицательные элементы вектора v?
Afor i=1:n; if v(i)<0; v(i)=0; end; end
Bv(v < 0) = 0
Cv = abs(v)
Dv = max(v)
3. Чем заменить ручной аккумулятор суммы в цикле?
Aprod(v)
Bsum(v)
Clength(v)
Dsort(v)