В 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-объектов на элемент

Дополнительные причины:

  1. Потеря кеш-локальности
    Python-объекты разрознены в памяти, NumPy — нет.

  2. Невозможность оптимизаций
    Python-цикл нельзя автоматически векторизовать или засимдить.

  3. Ухудшение читаемости намерения
    Цикл описывает как считать, векторизация — что считать.

Важно уточнение:

– циклы допустимы для управления логикой, но не для численных вычислений
– если алгоритм нельзя выразить векторно — цикл честнее, чем псевдовекторизация
– в таких случаях используют Numba, Cython или пишут C/Go-расширения

Коротко:
циклы в NumPy хуже, потому что они вытаскивают вычисления обратно на медленный Python-уровень, лишая NumPy его главного преимущества — быстрых низкоуровневых векторных операций.