import numba
from typing import List
@numba.njit
def test(a: List[int]) -> int:
return 1
test([i for i in range(2_000_000)])
занимает 2 секунды и масштабируется линейно в зависимости от размера списка.
Обертывание входного аргумента numba.typed.List
занимает еще больше времени. (все время уходит на звонок numba.typed.List
.
Тайминги не улучшатся, если функция вызывается несколько раз (хотя она определена только один раз), т. е. это не вопрос времени компиляции.
Есть ли способ сказать numba просто использовать список как есть?
В моем реальном приложении необработанные данные поступают из внешней библиотеки, которая не может напрямую возвращать массивы или списки чисел, а только списки Python.
Я использую numba 0.59.1 и Python 3.12 на 4-ядерном ноутбуке Ubuntu22 с 16 ГБ ОЗУ.
@roganjosh создает этот список в Python почти мгновенно.
Numba работает только с типизированными переменными. Ему необходимо не только проверить типы всех элементов, но и преобразовать весь список в типизированный список. Это неявное преобразование может быть особенно дорогостоящим, поскольку списки CPython, также известные как отраженные списки, содержат указатели на выделенные объекты, и каждый объект подсчитывает ссылки. Типизированные списки Numba однородны и содержат не ссылки, а непосредственно значения в них. Это гораздо более эффективно и похоже на массив Numpy с дополнительными функциями, такими как изменение размера.
Есть ли способ сказать numba просто использовать список как есть?
АФАИК, нет. Отраженные списки больше не поддерживаются в коде Numba. Работа с отраженными списками не только неэффективна, но и нарушает систему типов.
Лучший вариант — создать непосредственно типизированный список. Вот пример:
import numba as nb
# Quite fast part (less than 0.1 seconds)
reflected_lst = [i for i in range(2_000_000)]
# Slow part (3 seconds)
typed_lst = nb.typed.typedlist.List(reflected_lst)
# Very fast part (less than 2 µs) since `lst` is already a typed-list
test(typed_lst)
Как упоминал @roganjosh, обратите внимание, что генерация списка включена в ваш тест, но это занимает лишь небольшую часть времени выполнения (<5% на моей машине).
Обратите внимание, что процесс преобразования в Numba особенно дорог (в отличие от Numpy, см. комментарии ниже). На эту тему есть открытый вопрос. Цитирую разработчиков:
[...] мы пришли к выводу, что, вероятно, есть возможности для улучшения.
[...] эта проблема связана с деталями реализации.
На данный момент проблема все еще открыта и существует патч, немного улучшающий производительность конвертации, но она все равно довольно медленная.
Это действительно удивительный результат. Преобразование списка в однородный массив numpy не займет ничего похожего на такое время (только что проверено). Что бы он проверял, что еще не включило бы механизм numpy?
Теперь, когда я это говорю, я предполагаю, что ответ будет заключаться в том, что numpy предварительно скомпилирован, а это подсказка типа для компилятора с нуля.
Действительно, Numpy довольно быстро справляется с этой операцией, и ему также необходимо проверять типы, как это делает Numba. AFAIK, текущее создание списка Numba довольно неэффективно по сравнению с Numpy, и его можно значительно оптимизировать. Обратите внимание, что указание типа Numba не оказывает существенного влияния на время выполнения преобразования списка (<1%). Также обратите внимание, что время JIT-компиляции/проверки занимает незначительное время, за исключением, возможно, первого запуска предоставленного кода (я запускал его несколько раз, чтобы не включать его).
Я только что проверил, и на практике первый запуск занимает не такое уж огромное время (<20%).
@roganjosh Я добавил информацию об открытой проблеме с Numba по этой конкретной теме;)
Приятно, спасибо! Похоже, что ближе к концу это немного горячо, и, возможно, не будет решено. Тем не менее, действительно интересно узнать об этой потенциальной проблеме с неровными «массивами»; Я был совершенно не в курсе
Я до сих пор не понимаю: конечно, преобразование numba-списка Python в числовой список не должно занимать на порядки больше времени, чем Python, создающий список (что в Python означает объекты по всей куче) с нуля (или из другого списка через список понимание в этом отношении)
@Bananach, ну, для понимания списка тривиально давать смешанные типы: a = [1 if x // 2 == 0 else "hi" for x in range(10)]
поэтому Python не нужно заботиться о том, чтобы он был однородным. Numba делает это, и, конечно же, комментарии к этой связанной проблеме объясняют это - реализация плохая. Что еще сказать?
Время настолько неудачное, что я думаю, что это был бы отличный способ внести свой вклад в открытый исходный код, но на это есть более глубокая причина. Никто не мог просто оставить такую большую бородавку повсюду.
Вы генерируете список при вызове функции еще до отправки его декорированной функции. т. е.
numba
должен быть полностью вычислен еще до того, как он будет отправлен в функцию. Конечно, это будет работать во время Python и не может быть скомпилировано.