Комментарии 2
Ps:Numba часто обгоняет ручной C‑код, потому что LLVM‑оптимизатор умеет делать векторизацию (SIMD‑инструкции процессора), которую средний разработчик вручную писать не будет.
Для компиляции вашей динамической библиотеки вы всё равно должны вызвать какой-то компилятор. Причём можете с помощью CC и CFLAGS указать тот же clang и прописать -O3 -ffast -march=native и т.д. Тогда поставите себя в равные условия с Numba (ну кроме Numba IR и слоя оптимизации над ним, но это уже тонкие детали)
У меня есть подозрение, что векторизация не применилась как раз из-за того, что вы не указали нужные опции компиляции. На godbolt, clang 23.1 с указанием -O3 -mavx2 (т.к. серверное железо разное и результаты будут разные при march=native) ваш пример замечательно векторизуется.
Cпасибо! Я перепроверил/подразобрался в теме этой.
CC и CFLAGS реально применит авто векторизацию, а полученный код получается сопоставимый по скорости с LLVM.
Но в контексте моего бенчмарка(sum_of_squares.py) есть один нюанс в структуре данных (который можно тоже считать за не большой огрех в бенчмарке).
В примере на C-API цикл проходит по python списку с помощью PyList_GetItem. В каждой итерации происходит вызов функции C-API, извлечение PyObject*, проверка на float, конвертация.
Для оптимизатора компилятора это каждый раз сюрприз. Типо он просто не уверен, что следующий элемент будет лежать в памяти подряд. Следовательно - не может векторизовать такой цикл.
В случае с Numba я передавал ей непрерывный numpy.ndarray. Для LLVM это предсказуемый массив данных. Он векторизуется дефолтно. (если передам то, что передавал в c++ - Numba упадет в object режим, что чревато еще более долгим выполнением чем чистый python)
По итогу я передавал по разному данные в c++ и Numba. Ну тут косяк.
Если переписать пример на с++ расширении. Принимать не список, а сырой указатель на память, и добавить в setup.py флагиextra_compile_args=['-O3', '-march=native'], то результаты C-API и Numba по логике должны быть практически одинаковыми.
Надо будет в гитхаб статьи добавить компиляцию с нужными флагами и c++ переписать малясь.

Когда Python становится слишком медленным: пишем C‑расширение с нуля и сравниваем с pybind11 (бонус JIT‑компилятор)