Подтвердить что ты не робот

Почему 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()

У кого-нибудь есть объяснение?

4b9b3361

Ответ 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