Сравнение скорости суммирования массива Numpy и Matlab
Недавно я преобразовал MATLAB script в Python с помощью Numpy и обнаружил, что он работает значительно медленнее. Я ожидал подобную производительность, поэтому мне интересно, что я делаю что-то неправильно.
Как урезанный пример, я вручную суммирую геометрический ряд:
Версия MATLAB:
function s = array_sum(a, array_size, iterations)
s = zeros(array_size);
for m = 1:iterations
s = a + 0.5*s;
end
end
% benchmark code
array_size = 500
iterations = 500
a = randn(array_size)
f = @() array_sum(a, array_size, iterations);
fprintf('run time: %.2f ms\n', timeit(f)*1e3);
Версия Python/Numpy:
import numpy as np
import timeit
def array_sum(a, array_size, iterations):
s = np.zeros((array_size, array_size))
for m in range(iterations):
s = a + 0.5*s
return s
array_size = 500
iterations = 500
a = np.random.randn(array_size, array_size)
timeit_iterations = 10
t1 = timeit.timeit(lambda: array_sum(a, array_size, iterations),
number=timeit_iterations)
print("run time: {:.2f} ms".format(1e3*t1/timeit_iterations))
На моей машине MATLAB заканчивается через 58 мс. Версия Python работает в 292 мс, или на 5X медленнее.
Я также попытался ускорить код Python, добавив декоратор Numba JIT @jit('f8[:,:](i8, i8)', nopython=True)
, но время только упало до 236 мс (на 4 раза медленнее).
Это медленнее, чем я ожидал. Я использую timeit неправильно? Что-то не так с моим кодом Python?
EDIT: отредактировано так, что случайная матрица создается вне контрольной функции.
РЕДАКТИРОВАТЬ 2: Я использовал контрольную точку вместо Torch (вычисляя сумму как s = torch.add(s, 0.5, a)
), и она работает всего на 52 мс на моем компьютере!
Ответы
Ответ 1
По моему опыту, при использовании функции numba jit обычно быстрее развертывать операции массива в циклы. Поэтому я попытался переписать вашу функцию python как:
@jit(nopython=True, cache=True)
def array_sum_numba(a, array_size, iterations):
s = np.zeros((array_size, array_size))
for m in range(iterations):
for i in range(array_size):
for j in range(array_size):
s[i,j] = a[i,j] + 0.5 * s[i,j]
return s
И из любопытства я также протестировал версию @percusse с небольшой модификацией параметра:
def array_sum2(r, array_size, iterations):
s = np.zeros((array_size, array_size))
for m in range(iterations):
s /= 2
s += r
return s
Результаты тестирования на моей машине:
- исходная версия Время выполнения: 143.83 мс
- numba jitted loop version время запуска: 26.99 мс
- Время работы версии @percusse: 61,38 мс
Этот результат в моих ожиданиях. Стоит отметить, что я увеличил время итераций до 50, что приводит к некоторому значительному сокращению времени для версии numba.
Вкратце: код Python может быть значительно ускорен, если вы используете numba jit и записываете функцию в циклы. У меня нет Matlab на моей машине, чтобы проверить, но я думаю, что с numba версия python выполняется быстрее.
Ответ 2
Поскольку вы обновляете одну и ту же переменную, подходящую для операций inplace, вы можете обновить свою функцию как
def array_sum2(array_size, iterations):
s = np.zeros((array_size, array_size))
r = np.random.randn(array_size, array_size)
for m in range(iterations):
s /= 2
s += r
return s
Это дало следующее преимущество скорости на моей машине по сравнению с array_sum
run time: 157.32 ms
run time2: 672.43 ms
Ответ 3
Времена включают вызов randn
, а также суммирование:
In [68]: timeit array_sum(array_size, 0)
16.6 ms ± 436 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [69]: timeit array_sum(array_size, 1)
18.9 ms ± 293 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [70]: timeit array_sum(array_size, 20)
55.5 ms ± 131 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [71]: (55-16)/20
Out[71]: 1.95
Итак, это 16 мс для установки и 2 мс на итерацию. Тот же шаблон с 500 итерациями.
MATLAB делает некоторую компиляцию JIT. Я не знаю, здесь ли это или нет. Я не тестировал MATLAB. В Octave (нет timeit
)
>> t = time(); array_sum(500,0); (time()-t)*1000
ans = 13.704
>> t = time(); array_sum(500,1); (time()-t)*1000
ans = 16.219
>> t = time(); array_sum(500,20); (time()-t)*1000
ans = 82.346
>> t = time(); array_sum(500,500); (time()-t)*1000
ans = 1610.6
Octave random
работает быстрее, но каждая итерационная сумма медленнее.