Numba slow when assigning to an array?
arrays, numba, python
Solution
What is slow here is the arr.copy() function, not the write access to an array. Proof:
# -*- coding: utf-8 -*-
from numba import autojit
from Timer import Timer
import numpy as np
@autojit
def fast_sum_arr(arr, z):
#z = arr.copy()
M = len(arr)
for i in range(M):
z[i] += arr[i]
return z
def sum_arr(arr, z):
#z = arr.copy()
M = len(arr)
for i in range(M):
z[i] += arr[i]
return z
@autojit
def fast_sum_sclr(arr):
z = 0
M = len(arr)
for i in range(M):
z += arr[i]
return z
def sum_sclr(arr):
z = 0
M = len(arr)
for i in range(M):
z += arr[i]
return z
if __name__ == '__main__':
vec1 = np.ones(1000)
z = vec1.copy()
with Timer() as t0:
for i in range(10000):
pass
print "time for empty loop ", t0.secs
print
with Timer() as t1:
for i in range(10000):
sum_arr(vec1, z)
print "time for sum_arr [µs]: ", (t1.secs-t0.secs) / 10000 * 1e6
with Timer() as t1:
for i in range(10000):
fast_sum_arr(vec1, z)
print "time for fast_sum_arr [µs]: ", (t1.secs-t0.secs) / 10000 * 1e6
with Timer() as t1:
for i in range(10000):
sum_sclr(vec1)
print "time for sum_arr [µs]: ", (t1.secs-t0.secs) / 10000 * 1e6
with Timer() as t1:
for i in range(10000):
fast_sum_sclr(vec1)
print "time for fast_sum_arr [µs]: ", (t1.secs-t0.secs) / 10000 * 1e6
"""
time for empty loop 0.000312089920044
time for sum_arr [µs]: 432.02688694
time for fast_sum_arr [µs]: 7.43598937988
time for sum_arr [µs]: 284.574580193
time for fast_sum_arr [µs]: 5.74610233307
"""
Problem
Numba seems to be a great solution for accelerating the execution of numeric code. However, when there are assignments to an array Numba seems to be slower than standard Python code. Consider this example comparing four alternatives, with/without Numba, writing to an array/scalar: (The calculations were kept very simple on purpose, to focus on the issue, which is assignment to a scalar vs assignment to an array cell) ``` @autojit def fast_sum_arr(arr): z = arr.copy() M = len(arr) for i in range(M): z[i] += arr[i] return z def sum_arr(arr): z = arr.copy() M = len(arr) for i in range(M): z[i] += arr[i] return z @autojit def fast_sum_sclr(arr): z = 0 M = len(arr) for i in range(M): z += arr[i] return z def sum_sclr(arr): z = 0 M = len(arr) for i in range(M): z += arr[i] return z ``` Using IPython's %timeit to evaluate the four alternatives I got: ``` In [125]: %timeit fast_sum_arr(arr) 100 loops, best of 3: 10.8 ms per loop In [126]: %timeit sum_arr(arr) 100 loops, best of 3: 4.11 ms per loop In [127]: %timeit fast_sum_sclr(arr) 100000 loops, best of 3: 10 us per loop In [128]: %timeit sum_sclr(arr) 100 loops, best of 3: 2.93 ms per loop ``` sum_arr, which was not compiled with Numba is more than twice as fast as fast_sum_arr, which was compiled with Numba. On the other hand, fast_sum_sclr, which was compiled with Numba is more than two orders of magnitude faster than sum_sclr, which was not compiled with Numba. So Numba performs remarkably well the task of accelerating sum_sclr but actually makes sum_arr execute slower. The only difference between sum_sclr and sum_arr is that the former assigns to a scalar while the latter assigns to an array cell. I don't know if there is any relation, but I recently read the following on the blog http://www.phi-node.com/: "It turns out that when Numba is confronted with any construct it doesn't support directly, it switches to a (very) slow code path." The blog author got Numba to perform much faster using an if statement instead of Python's max(). Any insights on this? Thanks, FS