Векторизация: думать матрицами вместо циклов
Самый важный урок курса: почему в 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.