GitHub

@@ -11,7 +11,7 @@ kernelspec:

1111

name: python3

1212

---

131314-

(speed)=

14+

(numba_lecture)=

1515

```{raw} jupyter

1616

<div id="qe-notebook-header" align="right" style="text-align:right;">

1717

<a href="https://quantecon.org/" title="quantecon.org">

@@ -49,7 +49,7 @@ In an {doc}`earlier lecture <need_for_speed>` we discussed vectorization,

4949

which can improve execution speed by sending array processing operations in batch to efficient low-level code.

50505151

However, as {ref}`discussed in that lecture <numba-p_c_vectorization>`,

52-

traditional vectorization schemes, such as those found in MATLAB, Julia, and NumPy, have weaknesses.

52+

traditional vectorization schemes, such as those found in MATLAB and NumPy, have weaknesses.

53535454

* Highly memory-intensive for compound array operations

5555

* Ineffective or impossible for some algorithms.

@@ -59,16 +59,16 @@ One way to circumvent these problems is by using [Numba](https://numba.pydata.or

59596060

Numba compiles functions to native machine code instructions during runtime.

616162-

When it succeeds, Numba will be on par with machine code from low-level languages.

62+

When it succeeds, the result is performance comparable to compiled C or Fortran.

63636464

In addition, Numba can do other useful tricks, such as {ref}`multithreading` or

6565

interfacing with GPUs (through `numba.cuda`).

666667-

Numba's JIT compiler is in many ways similar to the JIT compiler in JULIA

67+

Numba's JIT compiler is in many ways similar to the JIT compiler in Julia

68686969

The main difference is that it is less ambitious, attempting to compile a smaller subset of the language.

707071-

Although this might sound like a defficiency, it is in some ways an advantage.

71+

Although this might sound like a deficiency, it is in some ways an advantage.

72727373

Numba is lean, easy to use, and very good at what it does.

7474

@@ -184,7 +184,7 @@ The basic idea is this:

184184

* Moreover, the types of *other variables* in `qm` *can be inferred once the input types are known*.

185185

* So the strategy of Numba and other JIT compilers is to *wait until the function is called*, and then compile.

186186187-

That's is called "just-in-time" compilation.

187+

That is called "just-in-time" compilation.

188188189189

Note that, if you make the call `qm(0.5, 10)` and then follow it with `qm(0.9,

190190

20)`, compilation only takes place on the first call.

@@ -193,50 +193,10 @@ This is because compiled code is cached and reused as required.

193193194194

This is why, in the code above, `time3` is smaller than `time2`.

195195196-197-198-

## Decorator Notation

199-200-

In the code above we created a JIT compiled version of `qm` via the call

201-202-

```{code-cell} ipython3

203-

qm_numba = jit(qm)

204-

```

205-206-

In practice this would typically be done using an alternative *decorator* syntax.

207-208-

(We discuss decorators in a {doc}`separate lecture <python_advanced_features>` but you can skip the details at this stage.)

209-210-

Specifically, to target a function for JIT compilation we can put `@jit` before the function definition.

211-212-

Here's what this looks like for `qm`

213-214-

```{code-cell} ipython3

215-

@jit

216-

def qm(x0, n):

217-

x = np.empty(n+1)

218-

x[0] = x0

219-

for t in range(n):

220-

x[t+1] = α * x[t] * (1 - x[t])

221-

return x

222-

```

223-224-

This is equivalent to adding `qm = jit(qm)` after the function definition.

225-226-

The following now uses the jitted version:

227-228-

```{code-cell} ipython3

229-

with qe.Timer(precision=4):

230-

qm(0.1, 100_000)

231-

```

232-233-

```{code-cell} ipython3

234-

with qe.Timer(precision=4):

235-

qm(0.1, 100_000)

196+

```{admonition} Remark

197+

In practice, rather than writing `qm_numba = jit(qm)`, we use *decorator* syntax and put `@jit` before the function definition. This is equivalent to adding `qm = jit(qm)` after the definition. We use this syntax throughout the rest of the lecture. (See {doc}`python_advanced_features` for more on decorators.)

236198

```

237199238-

Numba also provides several arguments for decorators to accelerate computation and cache functions -- see [here](https://numba.readthedocs.io/en/stable/user/performance-tips.html).

239-240200241201

## Type Inference

242202

@@ -253,41 +213,35 @@ This allows it to generate efficient native machine code, without having to call

253213254214

When Numba cannot infer all type information, it will raise an error.

255215256-

For example, in the (artificial) setting below, Numba is unable to determine the type of function `mean` when compiling the function `bootstrap`

216+

For example, in the setting below, Numba is unable to determine the type of the function `g` when compiling `iterate`

257217258218

```{code-cell} ipython3

259219

@jit

260-

def bootstrap(data, statistics, n_resamples):

261-

bootstrap_stat = np.empty(n_resamples)

262-

n = len(data)

263-

for i in range(n_resamples):

264-

resample = np.random.choice(data, size=n, replace=True)

265-

bootstrap_stat[i] = statistics(resample)

266-

return bootstrap_stat

267-268-

# No decorator here.

269-

def mean(data):

270-

return np.mean(data)

220+

def iterate(f, x0, n):

221+

x = x0

222+

for t in range(n):

223+

x = f(x)

224+

return x

271225272-

data = np.array((2.3, 3.1, 4.3, 5.9, 2.1, 3.8, 2.2))

273-

n_resamples = 10

226+

# Not jitted

227+

def g(x):

228+

return np.cos(x) - 2 * np.sin(x)

274229275230

# This code throws an error

276231

try:

277-

bootstrap(data, mean, n_resamples)

232+

iterate(g, 0.5, 100)

278233

except Exception as e:

279234

print(e)

280235

```

281236282-

We can fix this error easily in this case by compiling `mean`.

237+

We can fix this easily by compiling `g`.

283238284239

```{code-cell} ipython3

285240

@jit

286-

def mean(data):

287-

return np.mean(data)

241+

def g(x):

242+

return np.cos(x) - 2 * np.sin(x)

288243289-

with qe.Timer():

290-

bootstrap(data, mean, n_resamples)

244+

iterate(g, 0.5, 100)

291245

```

292246293247

@@ -334,6 +288,8 @@ function.

334288335289

When Numba compiles machine code for functions, it treats global variables as constants to ensure type stability.

336290291+

To avoid this, pass values as function arguments rather than relying on globals.

292+337293338294

(multithreading)=

339295

## Multithreaded Loops in Numba

@@ -363,8 +319,6 @@ distribution.

363319

Here's the code:

364320365321

```{code-cell} ipython3

366-

from numba import jit

367-368322

@jit

369323

def h(w, r=0.1, s=0.3, v1=0.1, v2=1.0):

370324

"""

@@ -459,7 +413,9 @@ with qe.Timer():

459413

The speed-up is significant.

460414461415

Notice that we parallelize across households rather than over time -- updates of

462-

an individual household across time periods are inherently sequential

416+

an individual household across time periods are inherently sequential.

417+418+

For GPU-based parallelization, see our {doc}`lectures on JAX <jax_intro>`.

463419464420

## Exercises

465421

Read the original on github.com ↗