****** 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 :term:`object mode`, and a few features are supported in :term:`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. * A small number of NumPy array ufuncs are only supported in object mode, but the vast majority work in nopython mode. * 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:: # force compilation in nopython mode @jit(nopython=True) 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; Loop-jitting will not be used by the compiler in this case because of the 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 User can inspect the loop-jitting by running `foo.inspect_types()`. For example, this:: @jit def foo(n): result = np.arange(n) x = 1 for i in range(n): x = result[i] # assign to variable 'x' result += x # reading variable 'x' return result foo(10) # trigger autojit foo.inspect_types() prints the following output, indicating a lifted loop:: foo (int64,) -------------------------------------------------------------------------------- # File: # --- LINE 1 --- @jit # --- LINE 2 --- def foo(n): # --- LINE 3 --- # label 0 # n.1 = n :: pyobject # del n # $0.1 = global(np: ) :: pyobject # $0.2 = getattr(attr=arange, value=$0.1) :: pyobject # del $0.1 # $0.4 = call $0.2(n.1, ) :: pyobject # del $0.2 # result = $0.4 :: pyobject # del $0.4 result = np.arange(n) # --- LINE 4 --- # --- LINE 5 --- # $const0.5 = const(int, 1) :: pyobject # x = $const0.5 :: pyobject # del x # del $const0.5 x = 1 # --- LINE 6 --- # jump 24.1 # label 24.1 # $const24.1.1 = const(LiftedLoop, LiftedLoop()) :: pyobject # $24.1.4 = call $const24.1.1(result, n.1, ) :: pyobject # del n.1 # del $const24.1.1 # $24.1.6 = exhaust_iter(count=1, value=$24.1.4) :: pyobject # del $24.1.4 # $24.1.5 = static_getitem(index=0, value=$24.1.6) :: pyobject # del $24.1.6 # x.1 = $24.1.5 :: pyobject # del $24.1.5 # jump 54 for i in range(n): # --- LINE 7 --- x = result[i] # assign to variable 'x' # --- LINE 8 --- # --- LINE 9 --- # label 54 # $54.3 = inplace_binop(rhs=x.1, lhs=result, fn=+) :: pyobject # del x.1 # del result # result.1 = $54.3 :: pyobject # del $54.3 result += x # reading variable 'x' # --- LINE 10 --- # $54.5 = cast(value=result.1) :: pyobject # del result.1 # return $54.5 return result # The function contains lifted loops # Loop at line 6 # Has 1 overloads # File: # --- LINE 1 --- @jit # --- LINE 2 --- def foo(n): # --- LINE 3 --- result = np.arange(n) # --- LINE 4 --- # --- LINE 5 --- x = 1 # --- LINE 6 --- # label 34 # $34.2 = iternext(value=$phi34.1) :: pair # $34.3 = pair_first(value=$34.2) :: int64 # $34.4 = pair_second(value=$34.2) :: bool # del $34.2 # $phi37.1 = $34.3 :: int64 # del $34.3 # branch $34.4, 37, 53 # label 21 # result.1 = result :: array(int64, 1d, C, nonconst) # del result # n.1 = n :: int64 # del n # $21.1 = global(range: ) :: range # $21.3 = call $21.1(n.1, ) :: (int64,) -> range_state64 # del n.1 # del $21.1 # $21.4 = getiter(value=$21.3) :: range_iter64 # del $21.3 # $phi34.1 = $21.4 :: range_iter64 # del $21.4 # jump 34 # label 37 # i = $phi37.1 :: int64 # del $phi37.1 for i in range(n): # --- LINE 7 --- # label 53 # del result.1 # del $phi37.1 # del $phi34.1 # del $34.4 # jump 54 # label 54 # $54.2 = build_tuple(items=[Var(x, (7))]) :: (int64 x 1) # del x # $54.3 = cast(value=$54.2) :: (int64 x 1) # del $54.2 # return $54.3 # $37.4 = getitem(index=i, value=result.1) :: int64 # del i # x = $37.4 :: int64 # del $37.4 # jump 34 x = result[i] # assign to variable 'x' # --- LINE 8 --- # --- LINE 9 --- result += x # reading variable 'x' # --- LINE 10 --- return result ================================================================================