Сравнение скорости. numpy vs python standard

Я сделал несколько экспериментов и обнаружил ряд случаев, когда стандартная random и math библиотека python быстрее, чем 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)

Ответ 1

numpy - это действительно улучшение производительности для больших блоков данных. Накладные расходы на то, чтобы блоки памяти располагались правильно, прежде чем заливать ndarray в c-скомпилированную функцию numpy, как правило, перегружают любое время, если массив не является относительно большим. Вот почему так много вопросов numpy в основном "Как взять этот код цикла и сделать его быстрым", и почему он считается допустимым вопросом в этом теге, где почти любой другой тег будет подбрасывать вас на проверку кода, прежде чем они пройдут мимо заглавие.

Итак, да, ваше наблюдение обобщаемо. Векторизация - это целое число numpy. numpy код, который не векторизован, всегда медленнее, чем голый код python, и, возможно, "неправильный", как взломать один грецкий орех с помощью отбойного молотка. Или найдите нужный инструмент или получите больше орехов.

Ответ 2

NumPy используется в основном для производительности с массивами. Это зависит от использования смежных блоков памяти и более эффективной итерации более низкого уровня. Применение математической функции NumPy на скаляре или вычисление случайного числа не являются векторизуемыми операциями. Это объясняет поведение, которое вы видите.

См. Также Каковы преимущества NumPy над регулярными списками Python?

И будет ли вообще целесообразно использовать стандартную библиотеку, а не NumPy для небольших (обычно скалярных) операций?

Редко, что узкое место для программы вызвано операциями с скалярами. На практике различия незначительны. Так что в любом случае все в порядке. Если вы уже используете NumPy, нет никакого вреда в продолжении использования операций NumPy для скаляров.

Стоит сделать специальный случай вычисления случайных чисел. Как и следовало ожидать, случайное число, выбранное с помощью random vs 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. Таким образом, существуют различия в том, как производятся случайные числа и доступная функциональность. Для тестирования или других целей вы можете захотеть создать последовательные "случайные" числа.