Почему numpy.power 60x медленнее, чем укладка?
Возможно, я делаю что-то странное, но, возможно, обнаружил удивительную потерю производительности при использовании numpy, кажется последовательным, независимо от используемой мощности. Например, когда x представляет собой случайный массив 100x100
x = numpy.power(x,3)
примерно в 60 раз медленнее, чем
x = x*x*x
График ускорения для различных размеров массивов показывает сладкое пятно с массивами размером 10k и последовательной скоростью 5-10x для других размеров.
![enter image description here]()
Код для проверки ниже на вашем собственном компьютере (немного грязный):
import numpy as np
from matplotlib import pyplot as plt
from time import time
ratios = []
sizes = []
for n in np.logspace(1,3,20).astype(int):
a = np.random.randn(n,n)
inline_times = []
for i in range(100):
t = time()
b = a*a*a
inline_times.append(time()-t)
inline_time = np.mean(inline_times)
pow_times = []
for i in range(100):
t = time()
b = np.power(a,3)
pow_times.append(time()-t)
pow_time = np.mean(pow_times)
sizes.append(a.size)
ratios.append(pow_time/inline_time)
plt.plot(sizes,ratios)
plt.title('Performance of inline vs numpy.power')
plt.ylabel('Nx speed-up using inline')
plt.xlabel('Array size')
plt.xscale('log')
plt.show()
У кого-нибудь есть объяснение?
Ответы
Ответ 1
Хорошо известно, что умножение двойников, что ваш процессор может сделать очень причудливо, очень, очень быстро. pow
заметно медленнее.
Некоторые руководства по производительности даже советуют людям планировать это, возможно, даже в некотором роде, которые могут быть немного переусердными в разы.
numpy special-cases squaring, чтобы убедиться, что он не слишком, слишком медленный, но он отправляет кубирование прямо в ваш libc pow
, что не так быстро, как умножение пары.
Ответ 2
Я подозреваю, что проблема заключается в том, что np.power
всегда выполняет float exponentiation, и он не знает, как оптимизировать или векторизовать это на вашей платформе (или, возможно, в большинстве/на всех платформах), в то время как умножение легко вбрасывается SSE и довольно быстро, даже если вы этого не сделаете.
Даже если np.power
были достаточно умны, чтобы выполнять целочисленное возведение в степень отдельно, если только он не разворачивает небольшие значения в повторное умножение, все равно будет не так быстро.
Вы можете легко это проверить, сравнив время для int-to-int, int-to-float, float-to-int и float-to-float powers против умножения для небольшого массива; int-to-int примерно в 5 раз быстрее, чем другие, но все же на 4 раза медленнее, чем умножение (хотя я тестировал с PyPy с настроенным NumPy, поэтому, вероятно, лучше для кого-то с обычным NumPy, установленным на CPython, чтобы дать реальные результаты...)
Ответ 3
Производительность функции мощности numpys очень нелинейна с показателем экспоненты. Покончите с этим наивным подходом. Такой же тип масштабирования должен существовать независимо от размера матрицы. В принципе, если показатель недостаточно велик, вы не увидите ощутимой выгоды.
import matplotlib.pyplot as plt
import numpy as np
import functools
import time
def timeit(func):
@functools.wraps(func)
def newfunc(*args, **kwargs):
startTime = time.time()
res = func(*args, **kwargs)
elapsedTime = time.time() - startTime
return (res, elapsedTime)
return newfunc
@timeit
def naive_power(m, n):
m = np.asarray(m)
res = m.copy()
for i in xrange(1,n):
res *= m
return res
@timeit
def fast_power(m, n):
# elementwise power
return np.power(m, n)
m = np.random.random((100,100))
n = 400
rs1 = []
ts1 = []
ts2 = []
for i in xrange(1, n):
r1, t1 = naive_power(m, i)
ts1.append(t1)
for i in xrange(1, n):
r2, t2 = fast_power(m, i)
ts2.append(t2)
plt.plot(ts1, label='naive')
plt.plot(ts2, label='numpy')
plt.xlabel('exponent')
plt.ylabel('time')
plt.legend(loc='upper left')
![performance plot]()