****** Arrays ****** Support for NumPy arrays is a key focus of Numba development and is currently undergoing extensive refactorization and improvement. Most capabilities of NumPy arrays are supported by Numba in object mode, and a few features are supported in nopython mode too (with much more to come). A few noteworthy limitations of arrays at this time: * Arrays can be passed in to a function in nopython mode, but not returned. Arrays can only be returned in object mode. * New arrays can only be created in object mode. * Currently there are no bounds checking for array indexing and slicing, although negative indices will wrap around correctly. * NumPy array ufunc support in nopython node is incomplete at this time. Most if not all ufuncs should work in object mode though. * Array slicing only works in object mode. Array Creation & Loop-Jitting ------------------------------ NumPy array creation is not supported in nopython mode. Numba mitigates this by automatically trying to jit loops in nopython mode. This allows for array creation at the top of a function while still getting almost all the performance of nopython mode. For example, the following simple function:: # compiled in object mode @jit def sum(x, y): array = np.arange(x * y).reshape(x, y) sum = 0 for i in range(x): for j in range(y): sum += array[i, j] return sum looks like the equivalent of the following after being compiled by Numba:: # compiled in nopython mode @njit def jitted_loop(array, x, y): sum = 0 for i in range(x): for j in range(y): sum += array[i, j] return sum # compiled in object mode @jit def sum(x, y): array = np.arange(x * y).reshape(x, y) return jitted_loop(array, x, y) Another consequence of array creation being restricted to object mode is that NumPy ufuncs that return the result as a new array are not allowed in nopython mode. Fortunately we can declare an output array at the top of our function and pass that in to the ufunc to store our result. For example, the following:: @jit def foo(): # initialize stuff # slow loop in object mode for i in range(x): result = np.multiply(a, b) should be rewritten like the following to take advantage of loop jitting:: @jit def foo(): # initialize stuff # create output array result = np.zeros(a.size) # jitted loop in nopython mode for i in range(x): np.multiply(a, b, result) Loop Jitting Constraints ------------------------- The current loop-jitting mechanism is very conservative. A loop must satisfy a set of constraints for loop-jitting to trigger. These constraints will be relaxed in further development. Currently, a loop is rejected if: * the loop contains return statements; * the loop binds a value to a variable that is read outside of the loop. The following is rejected due to a return statement in the loop:: @jit def foo(n): result = np.zeros(n) # Rejected loop-jitting candidate for i in range(n): result[i] = i # setitem is accepted if i > 10: return # return is not accepted return result The following is rejected due to an assigning to a variable read outside of the loop:: @jit def foo(n): result = np.zeros(n) x = 1 # Rejected loop-jitting candidate for i in range(n): x = result[i] # assign to variable 'x' result += x # reading variable 'x' return result The following is accepted:: @jit def foo(n): result = np.zeros(n) x = 1 # Accepted loop-jitting candidate for i in range(n): x = 2 x = 3 # 'x' is only written to return result The following is accepted:: @jit def foo(n): result = np.zeros(n) x = 1 # Accepted loop-jitting candidate for i in range(n): result[i] = x return result User can inspect the loop-jitting by running `foo.inspect_types()`:: foo (int32,) -> pyobject -------------------------------------------------------------------------------- # File: somefile.py # --- LINE 1 --- @jit # --- LINE 2 --- def foo(n): # --- LINE 3 --- # label 0 # $0.1 = global(numpy: ) :: pyobject # $0.2 = getattr(value=$0.1, attr=zeros) :: pyobject # result = call $0.2(n, ) :: pyobject result = numpy.zeros(n) # --- LINE 4 --- # x = const(, 1) :: pyobject x = 1 # --- LINE 5 --- # jump 58 # label 58 # $58.1 = global(foo__numba__loop21__: LiftedLoop()) :: pyobject # $58.2 = call $58.1(n, result, x, ) :: pyobject # jump 54 for i in range(n): # --- LINE 6 --- result[i] = x # --- LINE 7 --- # label 54 # return result return result # The function contains lifted loops # Loop at line 5 # Has 1 overloads # File: somefile.py # --- LINE 1 --- @jit # --- LINE 2 --- def foo(n): # --- LINE 3 --- result = numpy.zeros(n) # --- LINE 4 --- x = 1 # --- LINE 5 --- # label 34 # $34.1 = iternext(value=$21.3) :: int32 # $34.2 = itervalid(value=$21.3) :: bool # branch $34.2, 37, 53 # label 21 # $21.1 = global(range: ) :: range # $21.2 = call $21.1(n, ) :: (int32,) -> range_state32 # $21.3 = getiter(value=$21.2) :: range_iter32 # jump 34 # label 37 # $37.1 = $34.1 :: int32 # i = $37.1 :: int32 for i in range(n): # --- LINE 6 --- # label 53 # del $21.3 # jump 54 # label 54 # $54.1 = const(, None) :: none # return $54.1 # result[i] = x :: (array(float64, 1d, C), int64, float64) -> none # jump 34 result[i] = x # --- LINE 7 --- return result ================================================================================