Почему эта тривиальная числовая функция со списком ввода работает так медленно?

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 ГБ ОЗУ.

Вы генерируете список при вызове функции еще до отправки его декорированной функции. т. е. numba должен быть полностью вычислен еще до того, как он будет отправлен в функцию. Конечно, это будет работать во время Python и не может быть скомпилировано.

roganjosh 13.04.2024 17:17

@roganjosh создает этот список в Python почти мгновенно.

Bananach 13.04.2024 20:10
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
2
78
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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?

roganjosh 13.04.2024 17:38

Теперь, когда я это говорю, я предполагаю, что ответ будет заключаться в том, что numpy предварительно скомпилирован, а это подсказка типа для компилятора с нуля.

roganjosh 13.04.2024 17:42

Действительно, Numpy довольно быстро справляется с этой операцией, и ему также необходимо проверять типы, как это делает Numba. AFAIK, текущее создание списка Numba довольно неэффективно по сравнению с Numpy, и его можно значительно оптимизировать. Обратите внимание, что указание типа Numba не оказывает существенного влияния на время выполнения преобразования списка (<1%). Также обратите внимание, что время JIT-компиляции/проверки занимает незначительное время, за исключением, возможно, первого запуска предоставленного кода (я запускал его несколько раз, чтобы не включать его).

Jérôme Richard 13.04.2024 17:45

Я только что проверил, и на практике первый запуск занимает не такое уж огромное время (<20%).

Jérôme Richard 13.04.2024 17:54

@roganjosh Я добавил информацию об открытой проблеме с Numba по этой конкретной теме;)

Jérôme Richard 13.04.2024 18:13

Приятно, спасибо! Похоже, что ближе к концу это немного горячо, и, возможно, не будет решено. Тем не менее, действительно интересно узнать об этой потенциальной проблеме с неровными «массивами»; Я был совершенно не в курсе

roganjosh 13.04.2024 18:24

Я до сих пор не понимаю: конечно, преобразование numba-списка Python в числовой список не должно занимать на порядки больше времени, чем Python, создающий список (что в Python означает объекты по всей куче) с нуля (или из другого списка через список понимание в этом отношении)

Bananach 13.04.2024 20:12

@Bananach, ну, для понимания списка тривиально давать смешанные типы: a = [1 if x // 2 == 0 else "hi" for x in range(10)] поэтому Python не нужно заботиться о том, чтобы он был однородным. Numba делает это, и, конечно же, комментарии к этой связанной проблеме объясняют это - реализация плохая. Что еще сказать?

roganjosh 13.04.2024 20:26

Время настолько неудачное, что я думаю, что это был бы отличный способ внести свой вклад в открытый исходный код, но на это есть более глубокая причина. Никто не мог просто оставить такую ​​большую бородавку повсюду.

roganjosh 13.04.2024 20:37

Другие вопросы по теме