This example uses trunk version of numba at Github.
%pylab inline
Populating the interactive namespace from numpy and matplotlib
import numpy as np
from numba import jit
The most important api of numba is the decorator: jit
.
The jit
decorator returns a compiled version of the function using the input types and the output types of the function. You can optionally specify the type using out_type(in_type, ...)
syntax. Array inputs can be specified using [:,:]
appended to the type. If no type are specified, it watches for what types you call the function with and infers the type of the return. If there is a previously compiled version of the code available it uses it, if not it generates machine code for the function and then executes that code.
def sum(arr):
M, N = arr.shape
sum = 0.0
for i in range(M):
for j in range(N):
sum += arr[i,j]
return sum
fastsum = jit('f8(f8[:,:])')(sum)
flexsum = jit(sum)
arr2d = np.arange(600,dtype=float).reshape(20,30)
print(sum(arr2d))
print(fastsum(arr2d))
print(flexsum(arr2d))
print(flexsum(arr2d.astype(int)))
179700.0 179700.0 179700.0 179700.0
%timeit sum(arr2d)
10000 loops, best of 3: 135 µs per loop
%timeit fastsum(arr2d)
The slowest run took 8.39 times longer than the fastest. This could mean that an intermediate result is being cached. 1000000 loops, best of 3: 827 ns per loop
%timeit arr2d.sum()
The slowest run took 15.67 times longer than the fastest. This could mean that an intermediate result is being cached. 100000 loops, best of 3: 3.9 µs per loop
The speed-up is even more pronounced the more inner loops in the code. Here is an image processing example:
@jit('(f8[:,:],f8[:,:],f8[:,:])')
def filter(image, filt, output):
M, N = image.shape
m, n = filt.shape
for i in range(m//2, M-m//2):
for j in range(n//2, N-n//2):
result = 0.0
for k in range(m):
for l in range(n):
result += image[i+k-m//2,j+l-n//2]*filt[k, l]
output[i,j] = result
try:
# py2
from urllib import urlopen
except ImportError:
# py3
from urllib.request import urlopen
bytes = urlopen('http://www.cs.tut.fi/~foi/SA-DCT/original/image_Lake512.png').read()
from matplotlib.pyplot import imread
from io import BytesIO
image = imread(BytesIO(bytes)).astype('double')
imshow(image)
gray()
import time
filt = np.ones((15,15),dtype='double')
filt /= filt.sum()
output = image.copy()
filter(image, filt, output)
gray()
imshow(output)
start = time.time()
filter(image[:100,:100], filt, output[:100,:100])
fast = time.time() - start
start = time.time()
filter.py_func(image[:100,:100], filt, output[:100,:100])
slow = time.time() - start
print("Python: %f s; Numba: %f ms; Speed up is %f" % (slow, fast*1000, slow / fast))
Python: 1.250645 s; Numba: 2.591848 ms; Speed up is 482.530126
You can call Numba-created functions from other Numba-created functions and get even more amazing speed-ups.
@jit
def mandel(x, y, max_iters):
"""
Given the real and imaginary parts of a complex number,
determine if it is a candidate for membership in the Mandelbrot
set given a fixed number of iterations.
"""
i = 0
c = complex(x, y)
z = 0.0j
for i in range(max_iters):
z = z*z + c
if (z.real*z.real + z.imag*z.imag) >= 4:
return i
return 255
@jit
def create_fractal(min_x, max_x, min_y, max_y, image, iters):
height = image.shape[0]
width = image.shape[1]
pixel_size_x = (max_x - min_x) / width
pixel_size_y = (max_y - min_y) / height
for x in range(width):
real = min_x + x * pixel_size_x
for y in range(height):
imag = min_y + y * pixel_size_y
color = mandel(real, imag, iters)
image[y, x] = color
return image
image = np.zeros((500, 750), dtype=np.uint8)
imshow(create_fractal(-2.0, 1.0, -1.0, 1.0, image, 20))
jet()
%timeit create_fractal(-2.0, 1.0, -1.0, 1.0, image, 20)
100 loops, best of 3: 12.5 ms per loop
%timeit create_fractal.py_func(-2.0, 1.0, -1.0, 1.0, image, 20)
10 loops, best of 3: 175 ms per loop
Basic complex support is available as well. Some functions are still being implemented, however.
@jit
def complex_support(real, imag):
c = complex(real, imag)
return (c ** 2).conjugate()
c = 2.0 + 4.0j
complex_support(c.real, c.imag), (c**2).conjugate()
((-12-16j), (-12-16j))
We can even create a function that takes a structured array as input.
from numba import jit, typeof
import numpy as np
record_dtype = np.dtype([('x', np.float64), ('y', np.float64)], align=True)
record_type = typeof(record_dtype)
a = np.array([(1.0, 2.0), (3.0, 4.0)], dtype=record_dtype)
@jit
def hypot(data):
# return types of numpy functions are inferred
result = np.empty_like(data, dtype=np.float64)
# notice access to structure elements 'x' and 'y' via attribute access
# You can also index by field name or field index:
# data[i].x == data[i]['x'] == data[i][0]
for i in range(data.shape[0]):
result[i] = np.sqrt(data[i].x * data[i].x + data[i].y * data[i].y)
return result
print(hypot(a))
# Notice inferred return type
print(hypot.signatures)
# Notice native sqrt calls and for.body direct access to memory...
llvmir = hypot.inspect_llvm(hypot.signatures[0])
print(llvmir)
[ 2.23606798 5. ] [(array(Record([('x', '<f8'), ('y', '<f8')]), 1d, C),)] ; ModuleID = 'hypot' target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-darwin15.3.0" @.const.hypot = internal constant [6 x i8] c"hypot\00" @".const.Fatal error: missing _dynfunc.Closure" = internal constant [38 x i8] c"Fatal error: missing _dynfunc.Closure\00" @PyExc_RuntimeError = external global i8 @".const.missing Environment" = internal constant [20 x i8] c"missing Environment\00" define i32 @"__main__.hypot$8.array(Record_515,_1d,_C)"({ i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* noalias nocapture %retptr, { i8*, i32 }** noalias nocapture readnone %excinfo, i8* noalias nocapture readnone %env, i8* %arg.data.0, i8* nocapture readnone %arg.data.1, i64 %arg.data.2, i64 %arg.data.3, [16 x i8]* nocapture readonly %arg.data.4, i64 %arg.data.5.0, i64 %arg.data.6.0) { entry: %.4.i = icmp eq i8* %arg.data.0, null br i1 %.4.i, label %NRT_incref.exit, label %.3.endif.i, !prof !0 .3.endif.i: ; preds = %entry %.7.i = bitcast i8* %arg.data.0 to i64* %.4.i.i = atomicrmw add i64* %.7.i, i64 1 monotonic br label %NRT_incref.exit NRT_incref.exit: ; preds = %entry, %.3.endif.i %.50 = shl i64 %arg.data.5.0, 3 %.51 = tail call i8* @NRT_MemInfo_alloc_safe_aligned(i64 %.50, i32 32) %.5.i = getelementptr i8, i8* %.51, i64 24 %0 = bitcast i8* %.5.i to i8** %.6.i = load i8*, i8** %0, align 8 %.161 = icmp sgt i64 %arg.data.5.0, 0 br i1 %.161, label %B47.preheader, label %B112 B47.preheader: ; preds = %NRT_incref.exit %1 = add i64 %arg.data.5.0, 1 %scevgep31 = getelementptr [16 x i8], [16 x i8]* %arg.data.4, i64 0, i64 8 %scevgep3132 = bitcast i8* %scevgep31 to [16 x i8]* br label %B47 B47: ; preds = %B47.preheader, %B47 %lsr.iv33 = phi [16 x i8]* [ %scevgep3132, %B47.preheader ], [ %2, %B47 ] %lsr.iv29 = phi i8* [ %.6.i, %B47.preheader ], [ %scevgep, %B47 ] %lsr.iv = phi i64 [ %1, %B47.preheader ], [ %lsr.iv.next, %B47 ] %lsr.iv3335 = bitcast [16 x i8]* %lsr.iv33 to double* %lsr.iv2930 = bitcast i8* %lsr.iv29 to double* %scevgep36 = getelementptr double, double* %lsr.iv3335, i64 -1 %.306 = load double, double* %scevgep36, align 8 %.398 = load double, double* %lsr.iv3335, align 8 %.447 = fmul double %.398, %.398 %.355 = fmul double %.306, %.306 %.457 = fadd double %.355, %.447 %.471.le = tail call double @sqrt(double %.457) store double %.471.le, double* %lsr.iv2930, align 8 %lsr.iv.next = add i64 %lsr.iv, -1 %scevgep = getelementptr i8, i8* %lsr.iv29, i64 8 %scevgep34 = getelementptr [16 x i8], [16 x i8]* %lsr.iv33, i64 1, i64 0 %2 = bitcast i8* %scevgep34 to [16 x i8]* %.204 = icmp sgt i64 %lsr.iv.next, 1 br i1 %.204, label %B47, label %B112 B112: ; preds = %B47, %NRT_incref.exit %3 = icmp eq i8* %arg.data.0, null br i1 %3, label %NRT_decref.exit, label %.3.endif.i18, !prof !0 .3.endif.i18: ; preds = %B112 %.7.i16 = bitcast i8* %arg.data.0 to i64* %.4.i.i17 = atomicrmw sub i64* %.7.i16, i64 1 monotonic %.9.i = icmp eq i64 %.4.i.i17, 1 br i1 %.9.i, label %.3.endif.if.i, label %NRT_decref.exit, !prof !0 .3.endif.if.i: ; preds = %.3.endif.i18 tail call void @NRT_MemInfo_call_dtor(i8* nonnull %arg.data.0) br label %NRT_decref.exit NRT_decref.exit: ; preds = %B112, %.3.endif.i18, %.3.endif.if.i %retptr.repack37 = bitcast { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %retptr to i8** store i8* %.51, i8** %retptr.repack37, align 8 %retptr.repack1 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %retptr, i64 0, i32 1 store i8* null, i8** %retptr.repack1, align 8 %retptr.repack3 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %retptr, i64 0, i32 2 store i64 %arg.data.5.0, i64* %retptr.repack3, align 8 %retptr.repack5 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %retptr, i64 0, i32 3 store i64 8, i64* %retptr.repack5, align 8 %retptr.repack7 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %retptr, i64 0, i32 4 %4 = bitcast double** %retptr.repack7 to i8** store i8* %.6.i, i8** %4, align 8 %5 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %retptr, i64 0, i32 5, i64 0 store i64 %arg.data.5.0, i64* %5, align 8 %6 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %retptr, i64 0, i32 6, i64 0 store i64 8, i64* %6, align 8 ret i32 0 } declare noalias i8* @NRT_MemInfo_alloc_safe_aligned(i64, i32) ; Function Attrs: nounwind readonly declare double @sqrt(double) #0 define i8* @"cpython.__main__.hypot$8.array(Record_515,_1d,_C)"(i8* %py_closure, i8* %py_args, i8* nocapture readnone %py_kws) { entry: %.5 = alloca i8*, align 8 %.6 = call i32 (i8*, i8*, i64, i64, ...) @PyArg_UnpackTuple(i8* %py_args, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.const.hypot, i64 0, i64 0), i64 1, i64 1, i8** nonnull %.5) %.7 = icmp eq i32 %.6, 0 %.30 = alloca { i8*, i8*, i64, i64, [16 x i8]*, [1 x i64], [1 x i64] }, align 8 %.77 = alloca { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, align 8 %0 = bitcast { i8*, i8*, i64, i64, [16 x i8]*, [1 x i64], [1 x i64] }* %.30 to i8* call void @llvm.memset.p0i8.i64(i8* %0, i8 0, i64 56, i32 8, i1 false) br i1 %.7, label %entry.if, label %entry.endif, !prof !0 entry.if: ; preds = %entry.endif.endif.endif, %entry ret i8* null entry.endif: ; preds = %entry %.11 = icmp eq i8* %py_closure, null br i1 %.11, label %entry.endif.if, label %entry.endif.endif, !prof !0 entry.endif.if: ; preds = %entry.endif %.13 = call i32 @puts(i8* nonnull getelementptr inbounds ([38 x i8], [38 x i8]* @".const.Fatal error: missing _dynfunc.Closure", i64 0, i64 0)) unreachable entry.endif.endif: ; preds = %entry.endif %.15 = ptrtoint i8* %py_closure to i64 %.16 = add i64 %.15, 24 %.18 = inttoptr i64 %.16 to { i8* }* %.1910 = bitcast { i8* }* %.18 to i8** %.20 = load i8*, i8** %.1910, align 8 %.25 = icmp eq i8* %.20, null br i1 %.25, label %entry.endif.endif.if, label %entry.endif.endif.endif, !prof !0 entry.endif.endif.if: ; preds = %entry.endif.endif call void @PyErr_SetString(i8* nonnull @PyExc_RuntimeError, i8* nonnull getelementptr inbounds ([20 x i8], [20 x i8]* @".const.missing Environment", i64 0, i64 0)) ret i8* null entry.endif.endif.endif: ; preds = %entry.endif.endif %1 = bitcast { i8*, i8*, i64, i64, [16 x i8]*, [1 x i64], [1 x i64] }* %.30 to i8** %.29 = load i8*, i8** %.5, align 8 %.32 = bitcast { i8*, i8*, i64, i64, [16 x i8]*, [1 x i64], [1 x i64] }* %.30 to i8* %.33 = call i32 @NRT_adapt_ndarray_from_python(i8* %.29, i8* %.32) %.34 = icmp eq i32 %.33, 0 %2 = load i8*, i8** %1, align 8 br i1 %.34, label %entry.endif.endif.endif.endif, label %entry.if, !prof !1 entry.endif.endif.endif.endif: ; preds = %entry.endif.endif.endif %sunkaddr = ptrtoint { i8*, i8*, i64, i64, [16 x i8]*, [1 x i64], [1 x i64] }* %.30 to i64 %sunkaddr11 = add i64 %sunkaddr, 32 %sunkaddr12 = inttoptr i64 %sunkaddr11 to [16 x i8]** %3 = load [16 x i8]*, [16 x i8]** %sunkaddr12, align 8 %sunkaddr13 = ptrtoint { i8*, i8*, i64, i64, [16 x i8]*, [1 x i64], [1 x i64] }* %.30 to i64 %sunkaddr14 = add i64 %sunkaddr13, 40 %sunkaddr15 = inttoptr i64 %sunkaddr14 to i64* %4 = load i64, i64* %sunkaddr15, align 8 %.4.i.i = icmp eq i8* %2, null br i1 %.4.i.i, label %NRT_incref.exit.i, label %.3.endif.i.i, !prof !0 .3.endif.i.i: ; preds = %entry.endif.endif.endif.endif %.7.i.i = bitcast i8* %2 to i64* %.4.i.i.i = atomicrmw add i64* %.7.i.i, i64 1 monotonic, !noalias !2 br label %NRT_incref.exit.i NRT_incref.exit.i: ; preds = %.3.endif.i.i, %entry.endif.endif.endif.endif %.50.i = shl i64 %4, 3 %.51.i = call i8* @NRT_MemInfo_alloc_safe_aligned(i64 %.50.i, i32 32), !noalias !2 %.5.i.i = getelementptr i8, i8* %.51.i, i64 24 %5 = bitcast i8* %.5.i.i to i8** %.6.i.i = load i8*, i8** %5, align 8, !noalias !2 %.161.i = icmp sgt i64 %4, 0 br i1 %.161.i, label %B47.i.preheader, label %B112.i B47.i.preheader: ; preds = %NRT_incref.exit.i %6 = add i64 %4, 1 %scevgep4 = getelementptr [16 x i8], [16 x i8]* %3, i64 0, i64 8 %scevgep45 = bitcast i8* %scevgep4 to [16 x i8]* br label %B47.i B47.i: ; preds = %B47.i.preheader, %B47.i %lsr.iv6 = phi [16 x i8]* [ %scevgep45, %B47.i.preheader ], [ %7, %B47.i ] %lsr.iv2 = phi i8* [ %.6.i.i, %B47.i.preheader ], [ %scevgep, %B47.i ] %lsr.iv = phi i64 [ %6, %B47.i.preheader ], [ %lsr.iv.next, %B47.i ] %lsr.iv68 = bitcast [16 x i8]* %lsr.iv6 to double* %lsr.iv23 = bitcast i8* %lsr.iv2 to double* %scevgep9 = getelementptr double, double* %lsr.iv68, i64 -1 %.306.i = load double, double* %scevgep9, align 8, !noalias !2 %.398.i = load double, double* %lsr.iv68, align 8, !noalias !2 %.447.i = fmul double %.398.i, %.398.i %.355.i = fmul double %.306.i, %.306.i %.457.i = fadd double %.355.i, %.447.i %.471.le.i = call double @sqrt(double %.457.i), !noalias !2 store double %.471.le.i, double* %lsr.iv23, align 8, !noalias !2 %lsr.iv.next = add i64 %lsr.iv, -1 %scevgep = getelementptr i8, i8* %lsr.iv2, i64 8 %scevgep7 = getelementptr [16 x i8], [16 x i8]* %lsr.iv6, i64 1, i64 0 %7 = bitcast i8* %scevgep7 to [16 x i8]* %.204.i = icmp sgt i64 %lsr.iv.next, 1 br i1 %.204.i, label %B47.i, label %B112.i B112.i: ; preds = %B47.i, %NRT_incref.exit.i %8 = icmp eq i8* %2, null br i1 %8, label %"__main__.hypot$8.array(Record_515,_1d,_C).exit", label %.3.endif.i18.i, !prof !0 .3.endif.i18.i: ; preds = %B112.i %.7.i16.i = bitcast i8* %2 to i64* %.4.i.i17.i = atomicrmw sub i64* %.7.i16.i, i64 1 monotonic, !noalias !2 %.9.i.i = icmp eq i64 %.4.i.i17.i, 1 br i1 %.9.i.i, label %.3.endif.if.i.i, label %.3.endif.i, !prof !0 .3.endif.if.i.i: ; preds = %.3.endif.i18.i call void @NRT_MemInfo_call_dtor(i8* nonnull %2), !noalias !2 br label %.3.endif.i "__main__.hypot$8.array(Record_515,_1d,_C).exit": ; preds = %B112.i %9 = ptrtoint i8* %.51.i to i64 %10 = ptrtoint i8* %.6.i.i to i64 br label %NRT_decref.exit .3.endif.i: ; preds = %.3.endif.i18.i, %.3.endif.if.i.i %11 = bitcast i8* %2 to i64* %12 = ptrtoint i8* %.51.i to i64 %13 = ptrtoint i8* %.6.i.i to i64 %.4.i.i1 = atomicrmw sub i64* %11, i64 1 monotonic %.9.i = icmp eq i64 %.4.i.i1, 1 br i1 %.9.i, label %.3.endif.if.i, label %NRT_decref.exit, !prof !0 .3.endif.if.i: ; preds = %.3.endif.i %14 = ptrtoint i8* %.6.i.i to i64 %15 = ptrtoint i8* %.51.i to i64 call void @NRT_MemInfo_call_dtor(i8* nonnull %2) br label %NRT_decref.exit NRT_decref.exit: ; preds = %.3.endif.i, %"__main__.hypot$8.array(Record_515,_1d,_C).exit", %.3.endif.if.i %16 = phi i64 [ %14, %.3.endif.if.i ], [ %13, %.3.endif.i ], [ %10, %"__main__.hypot$8.array(Record_515,_1d,_C).exit" ] %17 = phi i64 [ %15, %.3.endif.if.i ], [ %12, %.3.endif.i ], [ %9, %"__main__.hypot$8.array(Record_515,_1d,_C).exit" ] %sunkaddr16 = ptrtoint i8* %.20 to i64 %sunkaddr17 = add i64 %sunkaddr16, 24 %sunkaddr18 = inttoptr i64 %sunkaddr17 to i8** %.75 = load i8*, i8** %sunkaddr18, align 8 %.76 = call i8* @PyList_GetItem(i8* %.75, i64 0) %18 = bitcast { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.77 to i64* store i64 %17, i64* %18, align 8 %19 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.77, i64 0, i32 1 %20 = bitcast i8** %19 to i64* store i64 0, i64* %20, align 8 %21 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.77, i64 0, i32 2 store i64 %4, i64* %21, align 8 %22 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.77, i64 0, i32 3 store i64 8, i64* %22, align 8 %23 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.77, i64 0, i32 4 %24 = bitcast double** %23 to i64* store i64 %16, i64* %24, align 8 %25 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.77, i64 0, i32 5, i64 0 store i64 %4, i64* %25, align 8 %26 = getelementptr inbounds { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }, { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.77, i64 0, i32 6, i64 0 store i64 8, i64* %26, align 8 %.79 = bitcast { i8*, i8*, i64, i64, double*, [1 x i64], [1 x i64] }* %.77 to i8* %.80 = call i8* @NRT_adapt_ndarray_to_python(i8* %.79, i32 1, i32 1, i8* %.76) ret i8* %.80 } declare i32 @PyArg_UnpackTuple(i8*, i8*, i64, i64, ...) ; Function Attrs: nounwind declare i32 @puts(i8* nocapture readonly) #1 declare void @PyErr_SetString(i8*, i8*) declare i32 @NRT_adapt_ndarray_from_python(i8* nocapture, i8* nocapture) declare i8* @PyList_GetItem(i8*, i64) declare i8* @NRT_adapt_ndarray_to_python(i8* nocapture, i32, i32, i8*) declare void @NRT_MemInfo_call_dtor(i8*) ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture, i8, i64, i32, i1) #2 attributes #0 = { nounwind readonly } attributes #1 = { nounwind } attributes #2 = { argmemonly nounwind } !0 = !{!"branch_weights", i32 1, i32 99} !1 = !{!"branch_weights", i32 99, i32 1} !2 = !{!3} !3 = distinct !{!3, !4, !"__main__.hypot$8.array(Record_515,_1d,_C): %retptr"} !4 = distinct !{!4, !"__main__.hypot$8.array(Record_515,_1d,_C)"}
print(hypot.signatures) # inspect function signature, note inferred return type
[(array(Record([('x', '<f8'), ('y', '<f8')]), 1d, C),)]
[line for line in llvmir.splitlines() if 'sqrt' in line] # note native math calls
[' %.471.le = tail call double @sqrt(double %.457)', 'declare double @sqrt(double) #0', ' %.471.le.i = call double @sqrt(double %.457.i), !noalias !2']
Have questions? Join the mailing list at numba-users@continuum.io