В NumPy циклы хуже не потому, что они «плохие вообще», а потому что они ломают модель вычислений NumPy.
Главная причина — Python-уровень.
Когда ты пишешь цикл for по элементам ndarray, происходит следующее:
– каждый шаг цикла — это интерпретируемый Python-код
– каждый элемент массива извлекается как Python-объект
– выполняются проверки типов, границ, вызовы методов
Это огромный overhead по сравнению с тем, ради чего NumPy создан.
Что делает NumPy вместо этого:
– хранит данные в плотном C-массиве
– выполняет операции в скомпилированных циклах (C / Fortran)
– использует SIMD и кеш-эффективный доступ к памяти
Сравнение по смыслу:
Цикл в Python:
for i in range(n):
y[i] = a[i] * b[i] + 1Векторизация:
y = a * b + 1Во втором случае:
– один вызов функции вместо n
– один проход по памяти
– ноль Python-объектов на элемент
Дополнительные причины:
-
Потеря кеш-локальности
Python-объекты разрознены в памяти, NumPy — нет. -
Невозможность оптимизаций
Python-цикл нельзя автоматически векторизовать или засимдить. -
Ухудшение читаемости намерения
Цикл описывает как считать, векторизация — что считать.
Важно уточнение:
– циклы допустимы для управления логикой, но не для численных вычислений
– если алгоритм нельзя выразить векторно — цикл честнее, чем псевдовекторизация
– в таких случаях используют Numba, Cython или пишут C/Go-расширения
Коротко:
циклы в NumPy хуже, потому что они вытаскивают вычисления обратно на медленный Python-уровень, лишая NumPy его главного преимущества — быстрых низкоуровневых векторных операций.