Repository: numba/numba

Issue:
SIMD vectorization failure with nditer
While implementing some `numpy` functionality with `@overload` I realized that my numba implementations were slower than pure numpy implementations.

Investigating further, I realized that with `@jit` and `@overload`, numba does not vectorize loops using SIMD instructions. I observed this effect on both 0.46 and 0.47.

Environment:

- Windows 10
- Intel i5-5300U
- numba environment built with `conda create -n numba47 -c numba python=3 numba jupyter`

As a simple test-case I reimplemented the simplest SIMD-optimizable function I could think of: `np.max()`.

I found:

- numpy uses SIMD: a `float64` array takes twice as long to process compared to a `float32` array
- the same is true for the built-in numba implementation (implemented with `@lower-builtin`)
- but if I copy-paste the numba implementation from `arraymath.py` and compile it with `@jit` or  `@overload`, the implementation is much slower. It takes 2x as long as the built-in implementation for `float64` and 4x as long for `float32`

Is this expected behaviour? If yes, is this documented anywhere?

What is it about `@lower-builtin` that allows vectorization using SIMD instructions but `@jit` not?

Is `@lower-builtin` currently the only way to compile using SIMD instructions?

------

Demonstration [jupyter notebook as a gist](https://gist.github.com/ARF1/28e8c0e64d72e62b59a293b0d937a1c8):

```

In [1]:

import numpy as np
from numba import jit
from numba.targets.imputils import (lower_builtin, impl_ret_borrowed,
                                    impl_ret_new_ref, impl_ret_untracked)
import numba.types as types

In [2]:

import llvmlite.binding as llvm
llvm.set_option('', '--debug-only=loop-vectorize')

In [3]:

def test_numpy_sum(arr):
    return np.sum(arr)

In [4]:

@jit(nopython=True, nogil=True, fastmath=True, error_model='numpy')
def test_numba_sum(arr):
    return np.sum(arr)

In [5]:

@jit(nopython=True, nogil=True, fastmath=True, error_model='numpy')
def test_numba_sum_own_impl(arr):
    c = 0
    for v in np.nditer(arr):
        c += v.item()
    return c

In [6]:

test_size = int(1e8)
test_arr = np.random.rand(test_size)
test_arr.dtype

Out[6]:

dtype('float64')

In [7]:

%%timeit test_numpy_sum(test_arr)
test_numpy_sum(test_arr)

161 ms ± 3.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [8]:

%%timeit
test_numba_sum(test_arr)

71.4 ms ± 3.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [9]:

%%timeit
test_numba_sum_own_impl(test_arr)

135 ms ± 3.28 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [10]:

test_arr = test_arr.astype(np.float32)
test_arr.dtype

Out[10]:

dtype('float32')

In [11]:

%%timeit test_numpy_sum(test_arr)
test_numpy_sum(test_arr)

90.8 ms ± 609 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [12]:

%%timeit
test_numba_sum(test_arr)

34 ms ± 1.15 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [13]:

%%timeit
test_numba_sum_own_impl(test_arr)

122 ms ± 1.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [14]:

print(test_numba_sum_own_impl.inspect_types())
```

Output is in the <details>
```
test_numba_sum_own_impl (array(float64, 1d, C),)
--------------------------------------------------------------------------------
# File: <ipython-input-5-893a416b656a>
# --- LINE 1 --- 

@jit(nopython=True, nogil=True, fastmath=True, error_model='numpy')

# --- LINE 2 --- 

def test_numba_sum_own_impl(arr):

    # --- LINE 3 --- 
    # label 0
    #   arr = arg(0, name=arr)  :: array(float64, 1d, C)
    #   $const2.0 = const(int, 0)  :: Literal[int](0)
    #   c = $const2.0  :: float64
    #   del $const2.0

    c = 0

    # --- LINE 4 --- 
    #   jump 6
    # label 6
    #   $8load_global.0 = global(np: <module 'numpy' from 'Z:\\Systemdateien\\Miniconda3\\envs\\numba47\\lib\\site-packages\\numpy\\__init__.py'>)  :: Module(<module 'numpy' from 'Z:\\Systemdateien\\Miniconda3\\envs\\numba47\\lib\\site-packages\\numpy\\__init__.py'>)
    #   $10load_method.1 = getattr(value=$8load_global.0, attr=nditer)  :: Function(<class 'numpy.nditer'>)
    #   del $8load_global.0
    #   $14call_method.3 = call $10load_method.1(arr, func=$10load_method.1, args=[Var(arr, <ipython-input-5-893a416b656a>:3)], kws=(), vararg=None)  :: (array(float64, 1d, C),) -> nditer(ndim=1, layout=C, inputs=(array(float64, 1d, C),))
    #   del arr
    #   del $10load_method.1
    #   $16get_iter.4 = getiter(value=$14call_method.3)  :: nditer(ndim=1, layout=C, inputs=(array(float64, 1d, C),))
    #   del $14call_method.3
    #   $phi18.0 = $16get_iter.4  :: nditer(ndim=1, layout=C, inputs=(array(float64, 1d, C),))
    #   del $16get_iter.4
    #   jump 18
    # label 18
    #   $18for_iter.1 = iternext(value=$phi18.0)  :: pair<array(float64, 0d, C), bool>
    #   $18for_iter.2 = pair_first(value=$18for_iter.1)  :: array(float64, 0d, C)
    #   $18for_iter.3 = pair_second(value=$18for_iter.1)  :: bool
    #   del $18for_iter.1
    #   $phi20.1 = $18for_iter.2  :: array(float64, 0d, C)
    #   del $18for_iter.2
    #   branch $18for_iter.3, 20, 36
    # label 20
    #   del $18for_iter.3
    #   v = $phi20.1  :: array(float64, 0d, C)
    #   del $phi20.1

    for v in np.nditer(arr):

        # --- LINE 5 --- 
        #   $26load_method.4 = getattr(value=v, attr=item)  :: BoundFunction(array.item for array(float64, 0d, C))
        #   del v
        #   $28call_method.5 = call $26load_method.4(func=$26load_method.4, args=[], kws=(), vararg=None)  :: () -> float64
        #   del $26load_method.4
        #   $30inplace_add.6 = inplace_binop(fn=<built-in function iadd>, immutable_fn=<built-in function add>, lhs=c, rhs=$28call_method.5, static_lhs=Undefined, static_rhs=Undefined)  :: float64
        #   del $28call_method.5
        #   c = $30inplace_add.6  :: float64
        #   del $30inplace_add.6
        #   jump 18
        # label 36

        c += v.item()

    # --- LINE 6 --- 
    #   del $phi20.1
    #   del $phi18.0
    #   del $18for_iter.3
    #   jump 38
    # label 38
    #   $40return_value.1 = cast(value=c)  :: float64
    #   del c
    #   return $40return_value.1

    return c


================================================================================
test_numba_sum_own_impl (array(float32, 1d, C),)
--------------------------------------------------------------------------------
# File: <ipython-input-5-893a416b656a>
# --- LINE 1 --- 

@jit(nopython=True, nogil=True, fastmath=True, error_model='numpy')

# --- LINE 2 --- 

def test_numba_sum_own_impl(arr):

    # --- LINE 3 --- 
    # label 0
    #   arr = arg(0, name=arr)  :: array(float32, 1d, C)
    #   $const2.0 = const(int, 0)  :: Literal[int](0)
    #   c = $const2.0  :: float64
    #   del $const2.0

    c = 0

    # --- LINE 4 --- 
    #   jump 6
    # label 6
    #   $8load_global.0 = global(np: <module 'numpy' from 'Z:\\Systemdateien\\Miniconda3\\envs\\numba47\\lib\\site-packages\\numpy\\__init__.py'>)  :: Module(<module 'numpy' from 'Z:\\Systemdateien\\Miniconda3\\envs\\numba47\\lib\\site-packages\\numpy\\__init__.py'>)
    #   $10load_method.1 = getattr(value=$8load_global.0, attr=nditer)  :: Function(<class 'numpy.nditer'>)
    #   del $8load_global.0
    #   $14call_method.3 = call $10load_method.1(arr, func=$10load_method.1, args=[Var(arr, <ipython-input-5-893a416b656a>:3)], kws=(), vararg=None)  :: (array(float32, 1d, C),) -> nditer(ndim=1, layout=C, inputs=(array(float32, 1d, C),))
    #   del arr
    #   del $10load_method.1
    #   $16get_iter.4 = getiter(value=$14call_method.3)  :: nditer(ndim=1, layout=C, inputs=(array(float32, 1d, C),))
    #   del $14call_method.3
    #   $phi18.0 = $16get_iter.4  :: nditer(ndim=1, layout=C, inputs=(array(float32, 1d, C),))
    #   del $16get_iter.4
    #   jump 18
    # label 18
    #   $18for_iter.1 = iternext(value=$phi18.0)  :: pair<array(float32, 0d, C), bool>
    #   $18for_iter.2 = pair_first(value=$18for_iter.1)  :: array(float32, 0d, C)
    #   $18for_iter.3 = pair_second(value=$18for_iter.1)  :: bool
    #   del $18for_iter.1
    #   $phi20.1 = $18for_iter.2  :: array(float32, 0d, C)
    #   del $18for_iter.2
    #   branch $18for_iter.3, 20, 36
    # label 20
    #   del $18for_iter.3
    #   v = $phi20.1  :: array(float32, 0d, C)
    #   del $phi20.1

    for v in np.nditer(arr):

        # --- LINE 5 --- 
        #   $26load_method.4 = getattr(value=v, attr=item)  :: BoundFunction(array.item for array(float32, 0d, C))
        #   del v
        #   $28call_method.5 = call $26load_method.4(func=$26load_method.4, args=[], kws=(), vararg=None)  :: () -> float32
        #   del $26load_method.4
        #   $30inplace_add.6 = inplace_binop(fn=<built-in function iadd>, immutable_fn=<built-in function add>, lhs=c, rhs=$28call_method.5, static_lhs=Undefined, static_rhs=Undefined)  :: float64
        #   del $28call_method.5
        #   c = $30inplace_add.6  :: float64
        #   del $30inplace_add.6
        #   jump 18
        # label 36

        c += v.item()

    # --- LINE 6 --- 
    #   del $phi20.1
    #   del $phi18.0
    #   del $18for_iter.3
    #   jump 38
    # label 38
    #   $40return_value.1 = cast(value=c)  :: float64
    #   del c
    #   return $40return_value.1

    return c


================================================================================
None
```
</details>

```
In [15]:

print(test_numba_sum_own_impl.inspect_llvm()[list(test_numba_sum_own_impl.inspect_llvm().keys())[0]])
```

Output is in the <details>
```assembler
; ModuleID = 'test_numba_sum_own_impl'
source_filename = "<string>"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-windows-msvc"

@"_ZN08NumbaEnv8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE" = common local_unnamed_addr global i8* null
@.const.test_numba_sum_own_impl = internal constant [24 x i8] c"test_numba_sum_own_impl\00"
@PyExc_RuntimeError = external global i8
@".const.missing Environment" = internal constant [20 x i8] c"missing Environment\00"
@PyExc_TypeError = external global i8
@".const.can't unbox array from PyObject into native value.  The object maybe of a different type" = internal constant [89 x i8] c"can't unbox array from PyObject into native value.  The object maybe of a different type\00"
@"_ZN08NumbaEnv5numba7targets8arrayobj15make_nditer_cls12$3clocals$3e6NdIter13init_specific12$3clocals$3e15check_shape$245E8UniTupleIxLi1EE8UniTupleIxLi1EE" = common local_unnamed_addr global i8* null

define i32 @"_ZN8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE"(double* noalias nocapture %retptr, { i8*, i32 }** noalias nocapture %excinfo, i8* %arg.arr.0, i8* nocapture readnone %arg.arr.1, i64 %arg.arr.2, i64 %arg.arr.3, double* nocapture readonly %arg.arr.4, i64 %arg.arr.5.0, i64 %arg.arr.6.0) local_unnamed_addr {
B6.endif:
  tail call void @NRT_incref(i8* %arg.arr.0)
  %.92 = icmp eq i64 %arg.arr.2, 0
  br i1 %.92, label %B36, label %B18.endif.preheader, !prof !0

B18.endif.preheader:                              ; preds = %B6.endif
  br label %B18.endif

B36:                                              ; preds = %B18.endif, %B6.endif
  %c.0.lcssa = phi double [ 0.000000e+00, %B6.endif ], [ %.328, %B18.endif ]
  tail call void @NRT_decref(i8* %arg.arr.0)
  store double %c.0.lcssa, double* %retptr, align 8
  ret i32 0

B18.endif:                                        ; preds = %B18.endif.preheader, %B18.endif
  %c.08 = phi double [ %.328, %B18.endif ], [ 0.000000e+00, %B18.endif.preheader ]
  %.94.07 = phi i64 [ %.232, %B18.endif ], [ 0, %B18.endif.preheader ]
  %scevgep = getelementptr double, double* %arg.arr.4, i64 %.94.07
  %.232 = add nuw nsw i64 %.94.07, 1
  %.319 = load double, double* %scevgep, align 8
  %.328 = fadd fast double %.319, %c.08
  %.233 = icmp slt i64 %.232, %arg.arr.2
  br i1 %.233, label %B18.endif, label %B36, !prof !1
}

define i8* @"_ZN7cpython8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE"(i8* nocapture readnone %py_closure, i8* %py_args, i8* nocapture readnone %py_kws) local_unnamed_addr {
entry:
  %.5 = alloca i8*, align 8
  %.6 = call i32 (i8*, i8*, i64, i64, ...) @PyArg_UnpackTuple(i8* %py_args, i8* getelementptr inbounds ([24 x i8], [24 x i8]* @.const.test_numba_sum_own_impl, i64 0, i64 0), i64 1, i64 1, i8** nonnull %.5)
  %.7 = icmp eq i32 %.6, 0
  %.21 = alloca { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, align 8
  %0 = bitcast { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.21 to i8*
  call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %0, i8 0, i64 56, i1 false)
  br i1 %.7, label %entry.if, label %entry.endif, !prof !0

entry.if:                                         ; preds = %entry.endif.endif.endif.thread, %entry
  ret i8* null

entry.endif:                                      ; preds = %entry
  %.11 = load i8*, i8** @"_ZN08NumbaEnv8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE", align 8
  %.16 = icmp eq i8* %.11, null
  br i1 %.16, label %entry.endif.if, label %entry.endif.endif, !prof !0

entry.endif.if:                                   ; preds = %entry.endif
  call void @PyErr_SetString(i8* nonnull @PyExc_RuntimeError, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @".const.missing Environment", i64 0, i64 0))
  ret i8* null

entry.endif.endif:                                ; preds = %entry.endif
  %.20 = load i8*, i8** %.5, align 8
  %.24 = bitcast { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.21 to i8*
  %1 = bitcast { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.21 to i8*
  call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %1, i8 0, i64 56, i1 false)
  %.25 = call i32 @NRT_adapt_ndarray_from_python(i8* %.20, i8* nonnull %.24)
  %2 = bitcast { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.21 to i8*
  %sunkaddr = getelementptr i8, i8* %2, i64 24
  %3 = bitcast i8* %sunkaddr to i64*
  %.29 = load i64, i64* %3, align 8
  %.30 = icmp ne i64 %.29, 8
  %.31 = icmp ne i32 %.25, 0
  %.32 = or i1 %.31, %.30
  br i1 %.32, label %entry.endif.endif.endif.thread, label %entry.endif.endif.endif.endif, !prof !0

entry.endif.endif.endif.thread:                   ; preds = %entry.endif.endif
  call void @PyErr_SetString(i8* nonnull @PyExc_TypeError, i8* getelementptr inbounds ([89 x i8], [89 x i8]* @".const.can't unbox array from PyObject into native value.  The object maybe of a different type", i64 0, i64 0))
  br label %entry.if

entry.endif.endif.endif.endif:                    ; preds = %entry.endif.endif
  %4 = bitcast { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.21 to i8**
  %.36.fca.0.load = load i8*, i8** %4, align 8
  %5 = bitcast { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.21 to i8*
  %sunkaddr2 = getelementptr i8, i8* %5, i64 16
  %6 = bitcast i8* %sunkaddr2 to i64*
  %.36.fca.2.load = load i64, i64* %6, align 8
  %7 = bitcast { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.21 to i8*
  %sunkaddr3 = getelementptr i8, i8* %7, i64 32
  %8 = bitcast i8* %sunkaddr3 to double**
  %.36.fca.4.load = load double*, double** %8, align 8
  %.43 = call i8* @PyEval_SaveThread()
  call void @NRT_incref(i8* %.36.fca.0.load), !noalias !2
  %.92.i = icmp eq i64 %.36.fca.2.load, 0
  br i1 %.92.i, label %entry.endif.endif.endif.endif.if.endif, label %B18.endif.i.preheader, !prof !0

B18.endif.i.preheader:                            ; preds = %entry.endif.endif.endif.endif
  br label %B18.endif.i

B18.endif.i:                                      ; preds = %B18.endif.i.preheader, %B18.endif.i
  %c.08.i = phi double [ %.328.i, %B18.endif.i ], [ 0.000000e+00, %B18.endif.i.preheader ]
  %.94.07.i = phi i64 [ %.232.i, %B18.endif.i ], [ 0, %B18.endif.i.preheader ]
  %scevgep = getelementptr double, double* %.36.fca.4.load, i64 %.94.07.i
  %.232.i = add nuw nsw i64 %.94.07.i, 1
  %.319.i = load double, double* %scevgep, align 8, !noalias !2
  %.328.i = fadd fast double %.319.i, %c.08.i
  %.233.i = icmp slt i64 %.232.i, %.36.fca.2.load
  br i1 %.233.i, label %B18.endif.i, label %entry.endif.endif.endif.endif.if.endif, !prof !1

entry.endif.endif.endif.endif.if.endif:           ; preds = %B18.endif.i, %entry.endif.endif.endif.endif
  %c.0.lcssa.i = phi double [ 0.000000e+00, %entry.endif.endif.endif.endif ], [ %.328.i, %B18.endif.i ]
  call void @PyEval_RestoreThread(i8* %.43)
  call void @NRT_decref(i8* %.36.fca.0.load), !noalias !2
  call void @NRT_decref(i8* %.36.fca.0.load)
  %.69 = call i8* @PyFloat_FromDouble(double %c.0.lcssa.i)
  ret i8* %.69
}

declare i32 @PyArg_UnpackTuple(i8*, i8*, i64, i64, ...) local_unnamed_addr

declare void @PyErr_SetString(i8*, i8*) local_unnamed_addr

declare i32 @NRT_adapt_ndarray_from_python(i8* nocapture, i8* nocapture) local_unnamed_addr

declare i8* @PyEval_SaveThread() local_unnamed_addr

declare void @PyEval_RestoreThread(i8*) local_unnamed_addr

declare i8* @PyFloat_FromDouble(double) local_unnamed_addr

; Function Attrs: noinline norecurse nounwind
define linkonce_odr void @NRT_incref(i8* %.1) local_unnamed_addr #0 {
.3:
  %.4 = icmp eq i8* %.1, null
  br i1 %.4, label %.3.if, label %.3.endif, !prof !0

.3.if:                                            ; preds = %.3
  ret void

.3.endif:                                         ; preds = %.3
  %.7 = bitcast i8* %.1 to i64*
  %.4.i = atomicrmw add i64* %.7, i64 1 monotonic
  ret void
}

; Function Attrs: noinline
define linkonce_odr void @NRT_decref(i8* %.1) local_unnamed_addr #1 {
.3:
  %.4 = icmp eq i8* %.1, null
  br i1 %.4, label %.3.if, label %.3.endif, !prof !0

.3.if:                                            ; preds = %.3.endif, %.3
  ret void

.3.endif:                                         ; preds = %.3
  fence release
  %.8 = bitcast i8* %.1 to i64*
  %.4.i = atomicrmw sub i64* %.8, i64 1 monotonic
  %.10 = icmp eq i64 %.4.i, 1
  br i1 %.10, label %.3.endif.if, label %.3.if, !prof !0

.3.endif.if:                                      ; preds = %.3.endif
  fence acquire
  tail call void @NRT_MemInfo_call_dtor(i8* nonnull %.1)
  ret void
}

declare void @NRT_MemInfo_call_dtor(i8*) local_unnamed_addr

; Function Attrs: argmemonly nounwind
declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #2

; Function Attrs: nounwind
declare void @llvm.stackprotector(i8*, i8**) #3

attributes #0 = { noinline norecurse nounwind }
attributes #1 = { noinline }
attributes #2 = { argmemonly nounwind }
attributes #3 = { nounwind }

!0 = !{!"branch_weights", i32 1, i32 99}
!1 = !{!"branch_weights", i32 99, i32 1}
!2 = !{!3}
!3 = distinct !{!3, !4, !"_ZN8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE: %retptr"}
!4 = distinct !{!4, !"_ZN8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE"}
```
</details>

```
In [16]:

print(test_numba_sum_own_impl.inspect_asm()[list(test_numba_sum_own_impl.inspect_asm().keys())[0]])
```

Output is in the <details>
```assembly
.text
	.file	"<string>"
	.globl	_ZN8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE
	.p2align	4, 0x90
	.type	_ZN8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE,@function
_ZN8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE:
	.cfi_startproc
	pushq	%rsi
	.cfi_def_cfa_offset 16
	pushq	%rdi
	.cfi_def_cfa_offset 24
	pushq	%rbx
	.cfi_def_cfa_offset 32
	subq	$48, %rsp
	vmovapd	%xmm6, 32(%rsp)
	.cfi_def_cfa_offset 80
	.cfi_offset %rbx, -32
	.cfi_offset %rdi, -24
	.cfi_offset %rsi, -16
	.cfi_offset %xmm6, -48
	movq	%r8, %rdi
	movq	%rcx, %rsi
	movq	112(%rsp), %rbx
	movabsq	$NRT_incref, %rax
	movq	%r8, %rcx
	callq	*%rax
	testq	%rbx, %rbx
	je	.LBB0_1
	movq	128(%rsp), %rax
	vxorpd	%xmm6, %xmm6, %xmm6
	xorl	%ecx, %ecx
	.p2align	4, 0x90
.LBB0_4:
	vaddsd	(%rax,%rcx,8), %xmm6, %xmm6
	addq	$1, %rcx
	cmpq	%rbx, %rcx
	jl	.LBB0_4
.LBB0_2:
	movabsq	$NRT_decref, %rax
	movq	%rdi, %rcx
	callq	*%rax
	vmovsd	%xmm6, (%rsi)
	xorl	%eax, %eax
	vmovaps	32(%rsp), %xmm6
	addq	$48, %rsp
	popq	%rbx
	popq	%rdi
	popq	%rsi
	retq
.LBB0_1:
	vxorpd	%xmm6, %xmm6, %xmm6
	jmp	.LBB0_2
.Lfunc_end0:
	.size	_ZN8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE, .Lfunc_end0-_ZN8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE
	.cfi_endproc

	.globl	_ZN7cpython8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE
	.p2align	4, 0x90
	.type	_ZN7cpython8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE,@function
_ZN7cpython8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	pushq	%r14
	pushq	%rsi
	pushq	%rdi
	pushq	%rbx
	andq	$-32, %rsp
	subq	$160, %rsp
	vmovapd	%xmm6, -48(%rbp)
	.cfi_offset %rbx, -48
	.cfi_offset %rdi, -40
	.cfi_offset %rsi, -32
	.cfi_offset %r14, -24
	.cfi_offset %xmm6, -64
	movq	%rdx, %rcx
	leaq	56(%rsp), %rax
	movq	%rax, 32(%rsp)
	movabsq	$.const.test_numba_sum_own_impl, %rdx
	movabsq	$PyArg_UnpackTuple, %rax
	movl	$1, %r8d
	movl	$1, %r9d
	callq	*%rax
	vxorpd	%xmm0, %xmm0, %xmm0
	vmovupd	%ymm0, 88(%rsp)
	vmovapd	%ymm0, 64(%rsp)
	testl	%eax, %eax
	je	.LBB1_1
	movabsq	$_ZN08NumbaEnv8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE, %rax
	cmpq	$0, (%rax)
	je	.LBB1_4
	movq	56(%rsp), %rcx
	vxorpd	%xmm0, %xmm0, %xmm0
	vmovupd	%ymm0, 88(%rsp)
	vmovapd	%ymm0, 64(%rsp)
	movabsq	$NRT_adapt_ndarray_from_python, %rax
	leaq	64(%rsp), %rdx
	vzeroupper
	callq	*%rax
	testl	%eax, %eax
	jne	.LBB1_8
	cmpq	$8, 88(%rsp)
	jne	.LBB1_8
	movq	64(%rsp), %rsi
	movq	80(%rsp), %rbx
	movq	96(%rsp), %rdi
	movabsq	$PyEval_SaveThread, %rax
	callq	*%rax
	movq	%rax, %r14
	movabsq	$NRT_incref, %rax
	movq	%rsi, %rcx
	callq	*%rax
	vxorpd	%xmm6, %xmm6, %xmm6
	testq	%rbx, %rbx
	je	.LBB1_12
	xorl	%eax, %eax
	.p2align	4, 0x90
.LBB1_11:
	vaddsd	(%rdi,%rax,8), %xmm6, %xmm6
	addq	$1, %rax
	cmpq	%rbx, %rax
	jl	.LBB1_11
.LBB1_12:
	movabsq	$PyEval_RestoreThread, %rax
	movq	%r14, %rcx
	callq	*%rax
	movabsq	$NRT_decref, %rdi
	movq	%rsi, %rcx
	callq	*%rdi
	movq	%rsi, %rcx
	callq	*%rdi
	movabsq	$PyFloat_FromDouble, %rax
	vmovapd	%xmm6, %xmm0
	callq	*%rax
.LBB1_2:
	vmovaps	-48(%rbp), %xmm6
	leaq	-32(%rbp), %rsp
	popq	%rbx
	popq	%rdi
	popq	%rsi
	popq	%r14
	popq	%rbp
	vzeroupper
	retq
.LBB1_4:
	movabsq	$PyExc_RuntimeError, %rcx
	movabsq	$".const.missing Environment", %rdx
	jmp	.LBB1_5
.LBB1_8:
	movabsq	$PyExc_TypeError, %rcx
	movabsq	$".const.can't unbox array from PyObject into native value.  The object maybe of a different type", %rdx
.LBB1_5:
	movabsq	$PyErr_SetString, %rax
	vzeroupper
	callq	*%rax
.LBB1_1:
	xorl	%eax, %eax
	jmp	.LBB1_2
.Lfunc_end1:
	.size	_ZN7cpython8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE, .Lfunc_end1-_ZN7cpython8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE
	.cfi_endproc

	.weak	NRT_incref
	.p2align	4, 0x90
	.type	NRT_incref,@function
NRT_incref:
	testq	%rcx, %rcx
	je	.LBB2_1
	lock		addq	$1, (%rcx)
	retq
.LBB2_1:
	retq
.Lfunc_end2:
	.size	NRT_incref, .Lfunc_end2-NRT_incref

	.weak	NRT_decref
	.p2align	4, 0x90
	.type	NRT_decref,@function
NRT_decref:
	.cfi_startproc
	testq	%rcx, %rcx
	je	.LBB3_2
	#MEMBARRIER
	lock		subq	$1, (%rcx)
	je	.LBB3_3
.LBB3_2:
	retq
.LBB3_3:
	#MEMBARRIER
	movabsq	$NRT_MemInfo_call_dtor, %rax
	rex64 jmpq	*%rax
.Lfunc_end3:
	.size	NRT_decref, .Lfunc_end3-NRT_decref
	.cfi_endproc

	.type	_ZN08NumbaEnv8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE,@object
	.comm	_ZN08NumbaEnv8__main__27test_numba_sum_own_impl$244E5ArrayIdLi1E1C7mutable7alignedE,8,8
	.type	.const.test_numba_sum_own_impl,@object
	.section	.rodata,"a",@progbits
	.p2align	4
.const.test_numba_sum_own_impl:
	.asciz	"test_numba_sum_own_impl"
	.size	.const.test_numba_sum_own_impl, 24

	.type	".const.missing Environment",@object
	.p2align	4
".const.missing Environment":
	.asciz	"missing Environment"
	.size	".const.missing Environment", 20

	.type	".const.can't unbox array from PyObject into native value.  The object maybe of a different type",@object
	.p2align	4
".const.can't unbox array from PyObject into native value.  The object maybe of a different type":
	.asciz	"can't unbox array from PyObject into native value.  The object maybe of a different type"
	.size	".const.can't unbox array from PyObject into native value.  The object maybe of a different type", 89

	.type	_ZN08NumbaEnv5numba7targets8arrayobj15make_nditer_cls12$3clocals$3e6NdIter13init_specific12$3clocals$3e15check_shape$245E8UniTupleIxLi1EE8UniTupleIxLi1EE,@object
	.comm	_ZN08NumbaEnv5numba7targets8arrayobj15make_nditer_cls12$3clocals$3e6NdIter13init_specific12$3clocals$3e15check_shape$245E8UniTupleIxLi1EE8UniTupleIxLi1EE,8,8

	.section	".note.GNU-stack","",@progbits
```
</details>

```
In [17]:

test_numba_sum_own_impl.inspect_cfg()[list(test_numba_sum_own_impl.inspect_cfg().keys())[0]].display(view=True)

Out[17]:

'Source.gv.pdf'
```

![image](https://user-images.githubusercontent.com/5834577/71783864-1f790180-2fed-11ea-81a1-6b564745c7aa.png)



Discussion:
Debugging loop-vectorization with `llvm.set_option('', '--debug-only=loop-vectorize')` yields:

```
LV: Checking a loop in "_ZN5numba7targets8arrayobj15make_nditer_cls12$3clocals$3e6NdIter13init_specific12$3clocals$3e15check_shape$242E8UniTupleIxLi1EE8UniTupleIxLi1EE" from check_shape
LV: Loop hints: force=? width=0 unroll=0
LV: Not vectorizing: Cannot prove legality.

LV: Checking a loop in "_ZN8__main__27test_numba_sum_own_impl$241E5ArrayIdLi1E1C7mutable7alignedE" from test_numba_sum_own_impl
LV: Loop hints: force=? width=0 unroll=0
LV: Found a loop: B18.endif
LV: Found an induction variable.
LV: Found a non-intrinsic callsite.
LV: Can't vectorize the instructions or CFG
LV: Not vectorizing: Cannot prove legality.

LV: Checking a loop in "_ZN7cpython8__main__27test_numba_sum_own_impl$241E5ArrayIdLi1E1C7mutable7alignedE" from test_numba_sum_own_impl
LV: Loop hints: force=? width=0 unroll=0
LV: Found a loop: B18.endif.i
LV: Found an induction variable.
LV: Found a non-intrinsic callsite.
LV: Can't vectorize the instructions or CFG
LV: Not vectorizing: Cannot prove legality.
```

That said, clearly this loop is vectorizable as demonstrated by numba itself with the built-in implementation...
Thanks for the report.

> Investigating further, I realized that with `@jit` and `@overload`, numba does not vectorize loops using SIMD instructions. I observed this effect on both 0.46 and 0.47.

I'm afraid this is incorrect. I think you may have just hit some particular case that refuses to optimise. A demonstrably working counter example:
```pycon
In [14]: from numba import njit                                                                                                 

In [15]: import numpy as np                                                                                                     

In [16]: def foo(x): 
    ...:     acc = 0 
    ...:     for i in range(len(x)): 
    ...:         acc += x[i] 
    ...:     return acc 
    ...:                                                                                                                        

In [17]: a = np.ones((100,))                                                                                                    

In [18]: foo(a)                                                                                                                 
Out[18]: 100.0

In [19]: jfoo = njit(fastmath=True)(foo)                                                                                        

In [20]: jfoo(a)                                                                                                                
Out[20]: 100.0

In [21]: print(jfoo.inspect_asm(jfoo.signatures[0]))          
```
Output of the print is in the details:
<details>

```asm
        .text
        .file   "<string>"
        .globl  _ZN8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE
        .p2align        4, 0x90
        .type   _ZN8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE,@function
_ZN8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE:
        movq    16(%rsp), %rax
        testq   %rax, %rax
        jle     .LBB0_1
        movq    8(%rsp), %rcx
        cmpq    $4, %rax
        jae     .LBB0_4
        xorpd   %xmm1, %xmm1
        xorl    %r8d, %r8d
        jmp     .LBB0_13
.LBB0_1:
        xorps   %xmm1, %xmm1
        movsd   %xmm1, (%rdi)
        xorl    %eax, %eax
        retq
.LBB0_4:
        movq    %rax, %r8
        andq    $-4, %r8
        leaq    -4(%r8), %rdx
        movq    %rdx, %rsi
        shrq    $2, %rsi
        incq    %rsi
        movl    %esi, %r9d
        andl    $3, %r9d
        cmpq    $12, %rdx
        jae     .LBB0_6
        xorpd   %xmm0, %xmm0
        xorl    %edx, %edx
        xorpd   %xmm1, %xmm1
        testq   %r9, %r9
        jne     .LBB0_9
        jmp     .LBB0_11
.LBB0_6:
        subq    %r9, %rsi
        xorpd   %xmm0, %xmm0
        xorl    %edx, %edx
        xorpd   %xmm1, %xmm1
        .p2align        4, 0x90
.LBB0_7:
        movupd  (%rcx,%rdx,8), %xmm2
        addpd   %xmm0, %xmm2
        movupd  16(%rcx,%rdx,8), %xmm0
        addpd   %xmm1, %xmm0
        movupd  32(%rcx,%rdx,8), %xmm1
        addpd   %xmm2, %xmm1
        movupd  48(%rcx,%rdx,8), %xmm2
        addpd   %xmm0, %xmm2
        movupd  64(%rcx,%rdx,8), %xmm3
        addpd   %xmm1, %xmm3
        movupd  80(%rcx,%rdx,8), %xmm4
        addpd   %xmm2, %xmm4
        movupd  96(%rcx,%rdx,8), %xmm0
        addpd   %xmm3, %xmm0
        movupd  112(%rcx,%rdx,8), %xmm1
        addpd   %xmm4, %xmm1
        addq    $16, %rdx
        addq    $-4, %rsi
        jne     .LBB0_7
        testq   %r9, %r9
        je      .LBB0_11
.LBB0_9:
        leaq    16(%rcx,%rdx,8), %rdx
        shlq    $5, %r9
        xorl    %esi, %esi
        .p2align        4, 0x90
.LBB0_10:
        movupd  -16(%rdx,%rsi), %xmm2
        addpd   %xmm2, %xmm0
        movupd  (%rdx,%rsi), %xmm2
        addpd   %xmm2, %xmm1
        addq    $32, %rsi
        cmpq    %rsi, %r9
        jne     .LBB0_10
.LBB0_11:
        addpd   %xmm1, %xmm0
        movapd  %xmm0, %xmm1
        unpckhpd        %xmm0, %xmm1
        addpd   %xmm0, %xmm1
        cmpq    %rax, %r8
        je      .LBB0_15
        subq    %r8, %rax
.LBB0_13:
        leaq    (%rcx,%r8,8), %rcx
        .p2align        4, 0x90
.LBB0_14:
        addsd   (%rcx), %xmm1
        leaq    8(%rcx), %rcx
        decq    %rax
        jg      .LBB0_14
.LBB0_15:
        movsd   %xmm1, (%rdi)
        xorl    %eax, %eax
        retq
.Lfunc_end0:
        .size   _ZN8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE, .Lfunc_end0-_ZN8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE

        .globl  _ZN7cpython8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE
        .p2align        4, 0x90
        .type   _ZN7cpython8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE,@function
_ZN7cpython8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE:
        .cfi_startproc
        subq    $104, %rsp
        .cfi_def_cfa_offset 112
        movq    %rsi, %rdi
        movabsq $.const.foo, %rsi
        movabsq $PyArg_UnpackTuple, %r9
        leaq    8(%rsp), %r8
        movl    $1, %edx
        movl    $1, %ecx
        xorl    %eax, %eax
        callq   *%r9
        xorpd   %xmm0, %xmm0
        movapd  %xmm0, 48(%rsp)
        movapd  %xmm0, 32(%rsp)
        movapd  %xmm0, 16(%rsp)
        movq    $0, 64(%rsp)
        testl   %eax, %eax
        je      .LBB1_4
        movabsq $_ZN08NumbaEnv8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE, %rax
        cmpq    $0, (%rax)
        je      .LBB1_2
        movq    8(%rsp), %rdi
        xorpd   %xmm0, %xmm0
        movapd  %xmm0, 48(%rsp)
        movapd  %xmm0, 32(%rsp)
        movapd  %xmm0, 16(%rsp)
        movq    $0, 64(%rsp)
        movabsq $NRT_adapt_ndarray_from_python, %rax
        leaq    16(%rsp), %rsi
        callq   *%rax
        testl   %eax, %eax
        jne     .LBB1_7
        cmpq    $8, 40(%rsp)
        jne     .LBB1_7
        movq    16(%rsp), %rdi
        movq    56(%rsp), %r9
        testq   %r9, %r9
        jle     .LBB1_9
        movq    48(%rsp), %rcx
        cmpq    $4, %r9
        jae     .LBB1_13
        xorpd   %xmm1, %xmm1
        xorl    %edx, %edx
        jmp     .LBB1_12
.LBB1_9:
        xorpd   %xmm1, %xmm1
        jmp     .LBB1_22
.LBB1_13:
        movq    %r9, %rdx
        andq    $-4, %rdx
        leaq    -4(%rdx), %rsi
        movq    %rsi, %rax
        shrq    $2, %rax
        incq    %rax
        movl    %eax, %r8d
        andl    $3, %r8d
        cmpq    $12, %rsi
        jae     .LBB1_15
        xorpd   %xmm0, %xmm0
        xorl    %esi, %esi
        xorpd   %xmm1, %xmm1
        testq   %r8, %r8
        jne     .LBB1_18
        jmp     .LBB1_20
.LBB1_15:
        subq    %r8, %rax
        xorpd   %xmm0, %xmm0
        xorl    %esi, %esi
        xorpd   %xmm1, %xmm1
        .p2align        4, 0x90
.LBB1_16:
        movupd  (%rcx,%rsi,8), %xmm2
        addpd   %xmm0, %xmm2
        movupd  16(%rcx,%rsi,8), %xmm0
        addpd   %xmm1, %xmm0
        movupd  32(%rcx,%rsi,8), %xmm1
        addpd   %xmm2, %xmm1
        movupd  48(%rcx,%rsi,8), %xmm2
        addpd   %xmm0, %xmm2
        movupd  64(%rcx,%rsi,8), %xmm3
        addpd   %xmm1, %xmm3
        movupd  80(%rcx,%rsi,8), %xmm4
        addpd   %xmm2, %xmm4
        movupd  96(%rcx,%rsi,8), %xmm0
        addpd   %xmm3, %xmm0
        movupd  112(%rcx,%rsi,8), %xmm1
        addpd   %xmm4, %xmm1
        addq    $16, %rsi
        addq    $-4, %rax
        jne     .LBB1_16
        testq   %r8, %r8
        je      .LBB1_20
.LBB1_18:
        leaq    16(%rcx,%rsi,8), %rax
        shlq    $5, %r8
        xorl    %esi, %esi
        .p2align        4, 0x90
.LBB1_19:
        movupd  -16(%rax,%rsi), %xmm2
        addpd   %xmm2, %xmm0
        movupd  (%rax,%rsi), %xmm2
        addpd   %xmm2, %xmm1
        addq    $32, %rsi
        cmpq    %rsi, %r8
        jne     .LBB1_19
.LBB1_20:
        addpd   %xmm1, %xmm0
        movapd  %xmm0, %xmm1
        unpckhpd        %xmm0, %xmm1
        addpd   %xmm0, %xmm1
        cmpq    %rdx, %r9
        jne     .LBB1_12
        jmp     .LBB1_22
.LBB1_2:
        movabsq $PyExc_RuntimeError, %rdi
        movabsq $".const.missing Environment", %rsi
        jmp     .LBB1_3
.LBB1_7:
        movabsq $PyExc_TypeError, %rdi
        movabsq $".const.can't unbox array from PyObject into native value.  The object maybe of a different type", %rsi
.LBB1_3:
        movabsq $PyErr_SetString, %rax
        callq   *%rax
.LBB1_4:
        xorl    %eax, %eax
        addq    $104, %rsp
        .cfi_def_cfa_offset 8
        retq
.LBB1_12:
        .cfi_def_cfa_offset 112
        addsd   (%rcx,%rdx,8), %xmm1
        incq    %rdx
        cmpq    %rdx, %r9
        jne     .LBB1_12
.LBB1_22:
        movapd  %xmm1, 80(%rsp)
        movabsq $NRT_decref, %rax
        callq   *%rax
        movabsq $PyFloat_FromDouble, %rax
        movaps  80(%rsp), %xmm0
        callq   *%rax
        addq    $104, %rsp
        .cfi_def_cfa_offset 8
        retq
.Lfunc_end1:
        .size   _ZN7cpython8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE, .Lfunc_end1-_ZN7cpython8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE
        .cfi_endproc

        .weak   NRT_decref
        .p2align        4, 0x90
        .type   NRT_decref,@function
NRT_decref:
        .cfi_startproc
        testq   %rdi, %rdi
        je      .LBB2_2
        #MEMBARRIER
        lock            decq    (%rdi)
        je      .LBB2_3
.LBB2_2:
        retq
.LBB2_3:
        #MEMBARRIER
        movabsq $NRT_MemInfo_call_dtor, %rax
        jmpq    *%rax
.Lfunc_end2:
        .size   NRT_decref, .Lfunc_end2-NRT_decref
        .cfi_endproc

        .type   _ZN08NumbaEnv8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE,@object
        .comm   _ZN08NumbaEnv8__main__7foo$243E5ArrayIdLi1E1C7mutable7alignedE,8,8
        .type   .const.foo,@object
        .section        .rodata,"a",@progbits
.const.foo:
        .asciz  "foo"
        .size   .const.foo, 4

        .type   ".const.missing Environment",@object
        .p2align        4
".const.missing Environment":
        .asciz  "missing Environment"
        .size   ".const.missing Environment", 20

        .type   ".const.can't unbox array from PyObject into native value.  The object maybe of a different type",@object
        .p2align        4
".const.can't unbox array from PyObject into native value.  The object maybe of a different type":
        .asciz  "can't unbox array from PyObject into native value.  The object maybe of a different type"
        .size   ".const.can't unbox array from PyObject into native value.  The object maybe of a different type", 89


        .section        ".note.GNU-stack","",@progbits
```

</details>

but note the vectorized loop body:
```asm
.LBB1_16:
        movupd  (%rcx,%rsi,8), %xmm2
        addpd   %xmm0, %xmm2
        movupd  16(%rcx,%rsi,8), %xmm0
        addpd   %xmm1, %xmm0
        movupd  32(%rcx,%rsi,8), %xmm1
        addpd   %xmm2, %xmm1
        movupd  48(%rcx,%rsi,8), %xmm2
        addpd   %xmm0, %xmm2
        movupd  64(%rcx,%rsi,8), %xmm3
        addpd   %xmm1, %xmm3
        movupd  80(%rcx,%rsi,8), %xmm4
        addpd   %xmm2, %xmm4
        movupd  96(%rcx,%rsi,8), %xmm0
        addpd   %xmm3, %xmm0
        movupd  112(%rcx,%rsi,8), %xmm1
        addpd   %xmm4, %xmm1
        addq    $16, %rsi
        addq    $-4, %rax
        jne     .LBB1_16
        testq   %r8, %r8
        je      .LBB1_20
```

> 
> Environment:
> 
>     * Windows 10
> 
>     * Intel i5-5300U
> 
>     * numba environment built with `conda create -n numba47 -c numba python=3 numba jupyter`
> 
> 
> As a simple test-case I reimplemented the simplest SIMD-optimizable function I could think of: `np.max()`.
> 
> I found:
> 
>     * numpy uses SIMD: a `float64` array takes twice as long to process compared to a `float64` array

Presume you mean float64 vs float32? Anaconda NumPy would be using Intel VML in the above case so yes.

>     * the same is true for the built-in numba implementation (implemented with `@lower-builtin`)
Yes.

>     * but if I copy-paste the numba implementation from `arraymath.py` and compile it with `@jit` or  `@overload`, the implementation is much slower. It takes 2x as long as the built-in implementation for `float64` and 4x as long for `float32`
> 
> 
> Is this expected behaviour? If yes, is this documented anywhere?

No it's probably a bug.

> What is it about `@lower-builtin` that allows vectorization using SIMD instructions but `@jit` not?

Nothing, it's the same compiler (LLVM) in the backend, it's most likely that there's some strange code gen going on that's causing a problem.

> Is `@lower-builtin` currently the only way to compile using SIMD instructions?

No, it's the the same compiler, see above.

> Demonstration [jupyter notebook as a gist](https://gist.github.com/ARF1/28e8c0e64d72e62b59a293b0d937a1c8):

<snip> thanks for the above, someone will take a look.
@stuartarchibald I fixed the `float32` vs. `float64` typo in OP. I can confirm that your code uses SIMD on my machine also. The problem seems to be the use of `nditer`.
```
In [1]: from numba import njit, jit
   ...: import numpy as np

In [2]: @njit(fastmath=True)
   ...: def foo(x):
   ...:     acc = 0
   ...:     for v in np.nditer(x):
   ...:         acc += v.item()
   ...:     return acc
   ...:

In [3]: a = np.ones((100,))

In [4]: foo(a)
Out[4]: 100.0

In [5]: print(foo.inspect_asm(foo.signatures[0]))
```

Output is in the <details>
```assembly
        .text
        .file   "<string>"
        .globl  _ZN8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE
        .p2align        4, 0x90
        .type   _ZN8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE,@function
_ZN8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE:
        .cfi_startproc
        pushq   %rsi
        .cfi_def_cfa_offset 16
        pushq   %rdi
        .cfi_def_cfa_offset 24
        pushq   %rbx
        .cfi_def_cfa_offset 32
        subq    $48, %rsp
        vmovapd %xmm6, 32(%rsp)
        .cfi_def_cfa_offset 80
        .cfi_offset %rbx, -32
        .cfi_offset %rdi, -24
        .cfi_offset %rsi, -16
        .cfi_offset %xmm6, -48
        movq    %r8, %rdi
        movq    %rcx, %rsi
        movq    112(%rsp), %rbx
        movabsq $NRT_incref, %rax
        movq    %r8, %rcx
        callq   *%rax
        testq   %rbx, %rbx
        je      .LBB0_1
        movq    128(%rsp), %rax
        vxorpd  %xmm6, %xmm6, %xmm6
        xorl    %ecx, %ecx
        .p2align        4, 0x90
.LBB0_4:
        vaddsd  (%rax,%rcx,8), %xmm6, %xmm6
        addq    $1, %rcx
        cmpq    %rbx, %rcx
        jl      .LBB0_4
.LBB0_2:
        movabsq $NRT_decref, %rax
        movq    %rdi, %rcx
        callq   *%rax
        vmovsd  %xmm6, (%rsi)
        xorl    %eax, %eax
        vmovaps 32(%rsp), %xmm6
        addq    $48, %rsp
        popq    %rbx
        popq    %rdi
        popq    %rsi
        retq
.LBB0_1:
        vxorpd  %xmm6, %xmm6, %xmm6
        jmp     .LBB0_2
.Lfunc_end0:
        .size   _ZN8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE, .Lfunc_end0-_ZN8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE
        .cfi_endproc

        .globl  _ZN7cpython8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE
        .p2align        4, 0x90
        .type   _ZN7cpython8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE,@function
_ZN7cpython8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset %rbp, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register %rbp
        pushq   %rsi
        pushq   %rdi
        pushq   %rbx
        andq    $-32, %rsp
        subq    $160, %rsp
        vmovapd %xmm6, -48(%rbp)
        .cfi_offset %rbx, -40
        .cfi_offset %rdi, -32
        .cfi_offset %rsi, -24
        .cfi_offset %xmm6, -64
        movq    %rdx, %rcx
        leaq    56(%rsp), %rax
        movq    %rax, 32(%rsp)
        movabsq $.const.foo, %rdx
        movabsq $PyArg_UnpackTuple, %rax
        movl    $1, %r8d
        movl    $1, %r9d
        callq   *%rax
        vxorpd  %xmm0, %xmm0, %xmm0
        vmovupd %ymm0, 88(%rsp)
        vmovapd %ymm0, 64(%rsp)
        testl   %eax, %eax
        je      .LBB1_1
        movabsq $_ZN08NumbaEnv8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE, %rax
        cmpq    $0, (%rax)
        je      .LBB1_4
        movq    56(%rsp), %rcx
        vxorpd  %xmm0, %xmm0, %xmm0
        vmovupd %ymm0, 88(%rsp)
        vmovapd %ymm0, 64(%rsp)
        movabsq $NRT_adapt_ndarray_from_python, %rax
        leaq    64(%rsp), %rdx
        vzeroupper
        callq   *%rax
        testl   %eax, %eax
        jne     .LBB1_8
        cmpq    $8, 88(%rsp)
        jne     .LBB1_8
        movq    64(%rsp), %rsi
        movq    80(%rsp), %rdi
        movq    96(%rsp), %rbx
        movabsq $NRT_incref, %rax
        movq    %rsi, %rcx
        callq   *%rax
        vxorpd  %xmm6, %xmm6, %xmm6
        testq   %rdi, %rdi
        je      .LBB1_12
        xorl    %eax, %eax
        .p2align        4, 0x90
.LBB1_11:
        vaddsd  (%rbx,%rax,8), %xmm6, %xmm6
        addq    $1, %rax
        cmpq    %rdi, %rax
        jl      .LBB1_11
.LBB1_12:
        movabsq $NRT_decref, %rdi
        movq    %rsi, %rcx
        callq   *%rdi
        movq    %rsi, %rcx
        callq   *%rdi
        movabsq $PyFloat_FromDouble, %rax
        vmovapd %xmm6, %xmm0
        callq   *%rax
.LBB1_2:
        vmovaps -48(%rbp), %xmm6
        leaq    -24(%rbp), %rsp
        popq    %rbx
        popq    %rdi
        popq    %rsi
        popq    %rbp
        vzeroupper
        retq
.LBB1_4:
        movabsq $PyExc_RuntimeError, %rcx
        movabsq $".const.missing Environment", %rdx
        jmp     .LBB1_5
.LBB1_8:
        movabsq $PyExc_TypeError, %rcx
        movabsq $".const.can't unbox array from PyObject into native value.  The object maybe of a different type", %rdx
.LBB1_5:
        movabsq $PyErr_SetString, %rax
        vzeroupper
        callq   *%rax
.LBB1_1:
        xorl    %eax, %eax
        jmp     .LBB1_2
.Lfunc_end1:
        .size   _ZN7cpython8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE, .Lfunc_end1-_ZN7cpython8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE
        .cfi_endproc

        .weak   NRT_incref
        .p2align        4, 0x90
        .type   NRT_incref,@function
NRT_incref:
        testq   %rcx, %rcx
        je      .LBB2_1
        lock            addq    $1, (%rcx)
        retq
.LBB2_1:
        retq
.Lfunc_end2:
        .size   NRT_incref, .Lfunc_end2-NRT_incref

        .weak   NRT_decref
        .p2align        4, 0x90
        .type   NRT_decref,@function
NRT_decref:
        .cfi_startproc
        testq   %rcx, %rcx
        je      .LBB3_2
        #MEMBARRIER
        lock            subq    $1, (%rcx)
        je      .LBB3_3
.LBB3_2:
        retq
.LBB3_3:
        #MEMBARRIER
        movabsq $NRT_MemInfo_call_dtor, %rax
        rex64 jmpq      *%rax
.Lfunc_end3:
        .size   NRT_decref, .Lfunc_end3-NRT_decref
        .cfi_endproc

        .type   _ZN08NumbaEnv8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE,@object
        .comm   _ZN08NumbaEnv8__main__7foo$241E5ArrayIdLi1E1C7mutable7alignedE,8,8
        .type   .const.foo,@object
        .section        .rodata,"a",@progbits
.const.foo:
        .asciz  "foo"
        .size   .const.foo, 4

        .type   ".const.missing Environment",@object
        .p2align        4
".const.missing Environment":
        .asciz  "missing Environment"
        .size   ".const.missing Environment", 20

        .type   ".const.can't unbox array from PyObject into native value.  The object maybe of a different type",@object
        .p2align        4
".const.can't unbox array from PyObject into native value.  The object maybe of a different type":
        .asciz  "can't unbox array from PyObject into native value.  The object maybe of a different type"
        .size   ".const.can't unbox array from PyObject into native value.  The object maybe of a different type", 89

        .type   _ZN08NumbaEnv5numba7targets8arrayobj15make_nditer_cls12$3clocals$3e6NdIter13init_specific12$3clocals$3e15check_shape$242E8UniTupleIxLi1EE8UniTupleIxLi1EE,@object
        .comm   _ZN08NumbaEnv5numba7targets8arrayobj15make_nditer_cls12$3clocals$3e6NdIter13init_specific12$3clocals$3e15check_shape$242E8UniTupleIxLi1EE8UniTupleIxLi1EE,8,8

        .section        ".note.GNU-stack","",@progbits
```
</details>

What is baffling me is that the built-in implementation also uses `nditer` but it still gets compiled with SIMD.

Is the built-in code somehow pre-compiled so that it does not pass through the compiler on my machine but was built with a better working compiler on the package-builder's machine?
note: updated title to reflect issue
Putting the sum-loop into another function helped. i.e

```python
from numba import njit, jit, float32
import numpy as np


@njit(fastmath=True)
def do_sum(x):
    acc = 0
    for v in np.nditer(x):
        acc += v.item()
    return acc


@njit(fastmath=True)
def foo(x):
    return do_sum(x)

a = np.ones((100,), dtype=np.float32)
foo(a)
print(foo.signatures[0])

foo.inspect_cfg(foo.signatures[0]).display(view=True)
```

Maybe it is because the loop optimizer to running twice and that is needed to produce simd-vectorized code.
just tested, not related to the number of times the optimizer is running.
This is likely about how numba is using llvm.

The following script prints out the llvm ir for `foo`:

```python
from numba import njit, jit
import numpy as np
from llvmlite import binding as llvm
llvm.set_option('', '--debug-only=loop-vectorize')

@njit(fastmath=True)
def foo(x):
    acc = 0
    for v in np.nditer(x):
        acc += v.item()
    return acc

a = np.ones((100,), dtype=np.float32)
foo(a)

with open('checkme.ll', 'w') as fout:
    print(foo.inspect_llvm(foo.signatures[0]), file=fout)
```

In the IR, `foo` is not simd-vectorized but running it through `opt -O1 -loop-vectorize` would produce simd code.


Note to self. Wonder if this is because the no python function needs the fast attr on it/the call from the cpython wrapper.
@sklam this is the same problem we discussed yesteday, `make_nditer` is inlined into main and then the main function inlined into the cpython wrapper, following this refop pruning is done but that's too late for the vectorizer which can't handle the non-intrinsic refops in the loop. Running a second refop prune and optimising permits vectorisation.

Resolving patch:
diff --git a/numba/core/codegen.py b/numba/core/codegen.py
--- a/numba/core/codegen.py
+++ b/numba/core/codegen.py
@@ -545,9 +545,14 @@ def _optimize_final_module(self):
         """
         Internal: optimize this library's final module.
         """
-        self._codegen._mpm.run(self._final_module)
+        # A cheaper optimisation pass is run first to try and get as many
+        # refops into the same function as possible via inlining
+        self._codegen._mpm_cheap.run(self._final_module)
+        # Refop pruning is then run on the heavily inlined function
         if not config.LLVM_REFPRUNE_PASS:
             self._final_module = remove_redundant_nrt_refct(self._final_module)
+        # The full optimisation suite is then run on the refop pruned IR
+        self._codegen._mpm_full.run(self._final_module)
 
     def _get_module_for_linking(self):
         """
@@ -1064,7 +1069,9 @@ def _init(self, llvm_module):
         self._engine = JitEngine(engine)
         self._target_data = engine.target_data
         self._data_layout = str(self._target_data)
-        self._mpm = self._module_pass_manager()
+        self._mpm_cheap = self._module_pass_manager(loop_vectorize=False,
+                                                    opt=1)
+        self._mpm_full = self._module_pass_manager()
 
         self._engine.set_object_cache(self._library_class._object_compiled_hook,
                                       self._library_class._object_getbuffer_hook)
@@ -1093,25 +1100,25 @@ def create_library(self, name):
     def unserialize_library(self, serialized):
         return self._library_class._unserialize(self, serialized)
 
-    def _module_pass_manager(self):
+    def _module_pass_manager(self, **kwargs):
         pm = ll.create_module_pass_manager()
         self._tm.add_analysis_passes(pm)
-        with self._pass_manager_builder() as pmb:
+        with self._pass_manager_builder(**kwargs) as pmb:
             pmb.populate(pm)
         if config.LLVM_REFPRUNE_PASS:
             pm.add_refprune_pass(_parse_refprune_flags())
         return pm
 
-    def _function_pass_manager(self, llvm_module):
+    def _function_pass_manager(self, llvm_module, **kwargs):
         pm = ll.create_function_pass_manager(llvm_module)
         self._tm.add_analysis_passes(pm)
-        with self._pass_manager_builder() as pmb:
+        with self._pass_manager_builder(**kwargs) as pmb:
             pmb.populate(pm)
         if config.LLVM_REFPRUNE_PASS:
             pm.add_refprune_pass(_parse_refprune_flags())
         return pm
 
-    def _pass_manager_builder(self):
+    def _pass_manager_builder(self, **kwargs):
         """
         Create a PassManagerBuilder.
 
@@ -1120,8 +1127,11 @@ def _pass_manager_builder(self):
         or function pass manager.  Otherwise some optimizations will be
         missed...
         """
+        opt_level = kwargs.pop('opt', config.OPT)
+        loop_vectorize = kwargs.pop('loop_vectorize', config.LOOP_VECTORIZE)
+
         pmb = lp.create_pass_manager_builder(
-            opt=config.OPT, loop_vectorize=config.LOOP_VECTORIZE)
+            opt=opt_level, loop_vectorize=loop_vectorize, **kwargs)
         return pmb
 
     def _check_llvm_bugs(self):


Test patch:
diff --git a/numba/tests/test_vectorization.py b/numba/tests/test_vectorization.py
new file mode 100644
--- /dev/null
+++ b/numba/tests/test_vectorization.py
@@ -0,0 +1,36 @@
+import numpy as np
+from numba import njit, types
+from unittest import TestCase
+from numba.tests.support import override_env_config
+
+_DEBUG = False
+if _DEBUG:
+    from llvmlite import binding as llvm
+    # Prints debug info from the LLVMs vectorizer
+    llvm.set_option("", "--debug-only=loop-vectorize")
+
+
+class TestVectorization(TestCase):
+    """
+    Tests to assert that code which should vectorize does indeed vectorize
+    """
+    def gen_ir(self, func, args_tuple, **flags):
+        with override_env_config(
+            "NUMBA_CPU_NAME", "skylake-avx512"
+        ), override_env_config("NUMBA_CPU_FEATURES", ""):
+            jobj = njit(**flags)(func)
+            jobj.compile(args_tuple)
+            ol = jobj.overloads[jobj.signatures[0]]
+            return ol.library.get_llvm_str()
+
+    def test_nditer_loop(self):
+        # see https://github.com/numba/numba/issues/5033
+        def do_sum(x):
+            acc = 0
+            for v in np.nditer(x):
+                acc += v.item()
+            return acc
+
+        llvm_ir = self.gen_ir(do_sum, (types.float64[::1],), fastmath=True)
+        self.assertIn("vector.body", llvm_ir)
+        self.assertIn("llvm.loop.isvectorized", llvm_ir)