Я провел несколько экспериментов и обнаружил ряд случаев, когда стандартные библиотеки Python random и math работают быстрее, чем аналог numpy.
Я думаю, что есть тенденция к тому, что стандартная библиотека python примерно в 10 раз быстрее для мелкомасштабных операций, в то время как numpy намного быстрее для крупномасштабных (векторных) операций. Я предполагаю, что у numpy есть некоторые накладные расходы, которые становятся доминирующими для небольших случаев.
Мой вопрос: верна ли моя интуиция? И будет ли вообще целесообразно использовать стандартную библиотеку вместо numpy для небольших (обычно скалярных) операций?
Примеры приведены ниже.
import math
import random
import numpy as np
Журнал и экспоненциальный
%timeit math.log(10)
# 158 ns ± 6.16 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit np.log(10)
# 1.64 µs ± 93.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit math.exp(3)
# 146 ns ± 8.57 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit np.exp(3)
# 1.72 µs ± 78.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Сгенерировать нормальное распределение
%timeit random.gauss(0, 1)
# 809 ns ± 12.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit np.random.normal()
# 2.57 µs ± 14.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Выбор случайного элемента
%timeit random.choices([1,2,3], k=1)
# 1.56 µs ± 55.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit np.random.choice([1,2,3], size=1)
# 23.1 µs ± 1.04 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
То же самое с массивом numpy
arr = np.array([1,2,3])
%timeit random.choices(arr, k=1)
# 1.72 µs ± 33.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit np.random.choice(arr, size=1)
# 18.4 µs ± 502 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
С большим массивом
arr = np.arange(10000)
%timeit random.choices(arr, k=1000)
# 401 µs ± 6.16 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit np.random.choice(arr, size=1000)
# 41.7 µs ± 1.39 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
@Julien поверхностно, но, может быть, какой-нибудь знаток сумеет вмешаться и соединить некоторые точки.
@Julien Хорошо, я хотел знать, можно ли обобщить мой эксперимент.
Я не эксперт, но да, есть некоторые накладные расходы с использованием numpy: native to numpy object (C++) преобразование / дополнительные проверки / правила безопасности и так далее ... если у вас есть небольшие массивы, обычно не быстрее использовать numpy, где именно это переломный момент, очень многое зависит от того, что именно вы делаете. Тестирование времени, как и вы, обычно является единственным способом выяснить, следует ли использовать numpy или оставаться нативным (если производительность является вашей единственной заботой, функции numpy могут быть бонусом независимо от производительности) ...
Как бы то ни было, это наблюдение было на моей стороне, и я хотел получить подтверждение. Так что спасибо за вопрос!






numpy действительно улучшает производительность только для больших блоков данных. Накладные расходы на то, чтобы убедиться, что блоки памяти выровнены правильно, перед заливкой ndarray в c-скомпилированную функцию numpy, как правило, подавляют любое преимущество во времени, если массив не является относительно большим. Вот почему так много вопросов numpy в основном сводятся к следующему: «Как мне взять этот зацикленный код и сделать его быстрым», и почему он считается допустимым вопросом в этом теге, где почти любой другой тег перебросит вас в Обзор кода, прежде чем они пройдут мимо заголовка. .
Итак, да, ваше наблюдение можно обобщить. Векторизация - это весь смысл numpy. Код numpy, который не векторизован, всегда медленнее, чем чистый код python, и, возможно, столь же «неправильный», как раскалывание одного грецкого ореха отбойным молотком. Либо найдите подходящий инструмент, либо получите больше орехов.
Значит, отбойный молоток - это правильный инструмент для раскалывания грецких орехов, если у вас их достаточно? ;-)
@PaulPanzer Скорее, программист - неподходящий инструмент для создания метафор.
NumPy используется в основном для производительности с массивы. Это зависит от использования непрерывных блоков памяти и более эффективной итерации нижнего уровня. Применение математической функции NumPy к скаляру или вычисление случайного числа не векторизуемые операции. Это объясняет наблюдаемое вами поведение.
См. Также В чем преимущества NumPy перед обычными списками Python?
And will it be in general advisable to use the standard library rather than NumPy for small (typically scalar) operations?
Редко, когда узкое место для программы вызвано операциями со скалярами. На практике различия незначительны. Так что в любом случае все в порядке. Если вы уже используете NumPy, нет ничего плохого в том, чтобы продолжать использовать операции NumPy на скалярах.
Стоит сделать особый случай вычисления случайных чисел. Как и следовало ожидать, случайное число, выбранное с помощью random и NumPy, может не совпадать:
assert random.gauss(0, 1) == np.random.normal() # AssertionError
assert random.choices(arr, k=1)[0] == np.random.choice(arr, size=1)[0] # AssertionError
У вас есть дополнительные функции в NumPy, позволяющие сделать случайные числа «предсказуемыми». Например, многократный запуск приведенного ниже сценария приведет только к одному и тому же результату:
np.random.seed(0)
np.random.normal()
То же самое и с np.random.choice. Таким образом, есть различия в как, случайное число выводится и доступные функции. Для тестирования или других целей вы можете захотеть получить согласованные «случайные» числа.
Спасибо. Я согласен, что такие ситуации, когда узкое место является критическим, редки. Мне довелось столкнуться с одним из них, потому что мне нужно было написать древовидный алгоритм, в котором я делаю небольшие вычисления на каждом узле при перемещении вниз по дереву, где векторизация не является простой или даже невозможной. Когда я переключился с numpy на стандарт Python, я увидел, что весь алгоритм работает в несколько раз быстрее. Также отмечу, что в стандартной библиотеке тоже есть random.seed. Я не думаю, что это что-то особенное для numpy.
@KotaMori, Вам стоит рассмотреть numba. Если вы можете поместить свой цикл в JIT-скомпилированную функцию, вы можете увидеть значительное улучшение производительности.
Похоже, вы сами ответили на свой вопрос ...