You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
To speed the function `qm` up using Numba, our first step is
120
+
Let's see how long this takes to run for large $n$
121
121
122
122
```{code-cell} ipython3
123
-
from numba import jit
123
+
n = 10_000_000
124
+
125
+
with qe.Timer() as timer1:
126
+
# Time Python base version
127
+
x = qm(0.1, int(n))
124
128
125
-
qm_numba = jit(qm)
126
129
```
127
130
128
-
The function `qm_numba` is a version of `qm` that is "targeted" for
129
-
JIT-compilation.
130
131
131
-
We will explain what this means momentarily.
132
+
#### Acceleration via Numba
133
+
134
+
To speed the function `qm` up using Numba, we first import the `jit` function
132
135
133
-
Let's time and compare identical function calls across these two versions, starting with the original function `qm`:
134
136
135
137
```{code-cell} ipython3
136
-
n = 10_000_000
138
+
from numba import jit
139
+
```
137
140
138
-
with qe.Timer() as timer1:
139
-
qm(0.1, int(n))
140
-
time1 = timer1.elapsed
141
+
Now we apply it to `qm`, producing a new function:
142
+
143
+
```{code-cell} ipython3
144
+
qm_numba = jit(qm)
141
145
```
142
146
143
-
Now let's try qm_numba
147
+
The function `qm_numba` is a version of `qm` that is "targeted" for
148
+
JIT-compilation.
149
+
150
+
We will explain what this means momentarily.
151
+
152
+
Let's time this new version:
144
153
145
154
```{code-cell} ipython3
146
155
with qe.Timer() as timer2:
147
-
qm_numba(0.1, int(n))
148
-
time2 = timer2.elapsed
156
+
# Time jitted version
157
+
x = qm_numba(0.1, int(n))
149
158
```
150
159
151
-
This is already a very large speed gain.
160
+
This is a large speed gain.
152
161
153
-
In fact, the next time and all subsequent times it runs even faster as the function has been compiled and is in memory:
162
+
In fact, the next time and all subsequent times it runs even faster as the
163
+
function has been compiled and is in memory:
154
164
155
165
(qm_numba_result)=
156
166
157
167
```{code-cell} ipython3
158
168
with qe.Timer() as timer3:
159
-
qm_numba(0.1, int(n))
160
-
time3 = timer3.elapsed
169
+
# Second run
170
+
x = qm_numba(0.1, int(n))
161
171
```
162
172
173
+
Here's the speed gain
174
+
163
175
```{code-cell} ipython3
164
-
time1 / time3 # Calculate speed gain
176
+
timer1.elapsed / timer3.elapsed
165
177
```
166
178
179
+
This is a big boost for a small modification to our original code.
180
+
181
+
Let's discuss how this works.
167
182
168
183
### How and When it Works
169
184
170
-
Numba attempts to generate fast machine code using the infrastructure provided by the [LLVM Project](https://llvm.org/).
185
+
Numba attempts to generate fast machine code using the infrastructure provided
186
+
by the [LLVM Project](https://llvm.org/).
171
187
172
188
It does this by inferring type information on the fly.
173
189
174
190
(See our {doc}`earlier lecture <need_for_speed>` on scientific computing for a discussion of types.)
175
191
176
192
The basic idea is this:
177
193
178
-
* Python is very flexible and hence we could call the function qm with many
179
-
types.
194
+
* Python is very flexible and hence we could call the function qm with many types.
180
195
* e.g., `x0` could be a NumPy array or a list, `n` could be an integer or a float, etc.
181
196
* This makes it very difficult to generate efficient machine code *ahead of time* (i.e., before runtime).
182
197
* However, when we do actually *call* the function, say by running `qm(0.5, 10)`,
183
-
the types of `x0`and `n`become clear.
198
+
the types of `x0`, `α`and `n`are determined.
184
199
* Moreover, the types of *other variables* in `qm`*can be inferred once the input types are known*.
185
200
* So the strategy of Numba and other JIT compilers is to *wait until the function is called*, and then compile.
186
201
187
202
That is called "just-in-time" compilation.
188
203
189
-
Note that, if you make the call `qm(0.5, 10)` and then follow it with `qm(0.9,
190
-
20)`, compilation only takes place on the first call.
204
+
Note that, if you make the call `qm_numba(0.5, 10)` and then follow it with `qm_numba(0.9, 20)`, compilation only takes place on the first call.
191
205
192
206
This is because compiled code is cached and reused as required.
193
207
194
-
This is why, in the code above, `time3` is smaller than `time2`.
208
+
This is why, in the code above, the second run of `qm_numba` is faster.
195
209
196
210
```{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.)
211
+
In practice, rather than writing `qm_numba = jit(qm)`, we typically use
212
+
*decorator* syntax and put `@jit` before the function definition. This is
213
+
equivalent to adding `qm = jit(qm)` after the definition.
198
214
```
199
215
200
216
201
-
## Type Inference
217
+
## Sharp Bits
202
218
203
-
Successful type inference is a key part of JIT compilation.
219
+
Numba is relatively easy to use but not always seamless.
204
220
205
-
As you can imagine, inferring types is easier for simple Python objects (e.g.,
206
-
simple scalar data types such as floats and integers).
221
+
Let's review some of the issues users run into.
207
222
208
-
Numba also plays well with NumPy arrays, which have well-defined types.
223
+
### Typing
209
224
210
-
In an ideal setting, Numba can infer all necessary type information.
225
+
Successful type inference is the key to JIT compilation.
211
226
212
-
This allows it to generate efficient native machine code, without having to call the Python runtime environment.
227
+
In an ideal setting, Numba can infer all necessary type information.
213
228
214
-
When Numba cannot infer all type information, it will raise an error.
229
+
When Numba *cannot* infer all type information, it will raise an error.
215
230
216
-
For example, in the setting below, Numba is unable to determine the type of the function `g` when compiling `iterate`
231
+
For example, in the setting below, Numba is unable to determine the type of the
232
+
function `g` when compiling `iterate`
217
233
218
234
```{code-cell} ipython3
219
235
@jit
@@ -234,7 +250,7 @@ except Exception as e:
234
250
print(e)
235
251
```
236
252
237
-
We can fix this easily by compiling `g`.
253
+
In the present case, we can fix this easily by compiling `g`.
238
254
239
255
```{code-cell} ipython3
240
256
@jit
@@ -244,28 +260,16 @@ def g(x):
244
260
iterate(g, 0.5, 100)
245
261
```
246
262
263
+
In other cases, such as when we want to use functions from external libaries
264
+
such as `SciPy`, there might not be any easy workaround.
247
265
248
-
## Dangers and Limitations
249
-
250
-
Let's add some cautionary notes.
251
-
252
-
### Limitations
253
-
254
-
As we've seen, Numba needs to infer type information on
255
-
all variables to generate fast machine-level instructions.
256
266
257
-
For large routines or those using external libraries, this process can easily fail.
267
+
### Global Variables
258
268
259
-
Hence, it's best to focus on speeding up small, time-critical snippets of code.
269
+
Another thing to be careful about when using Numba is handling of global
270
+
variables.
260
271
261
-
This will give you much better performance than blanketing your Python programs with `@jit` statements.
262
-
263
-
264
-
### A Gotcha: Global Variables
265
-
266
-
Here's another thing to be careful about when using Numba.
267
-
268
-
Consider the following example
272
+
For example, consider the following code
269
273
270
274
```{code-cell} ipython3
271
275
a = 1
@@ -284,9 +288,10 @@ print(add_a(10))
284
288
```
285
289
286
290
Notice that changing the global had no effect on the value returned by the
287
-
function.
291
+
function 😱.
288
292
289
-
When Numba compiles machine code for functions, it treats global variables as constants to ensure type stability.
293
+
When Numba compiles machine code for functions, it treats global variables as
294
+
constants to ensure type stability.
290
295
291
296
To avoid this, pass values as function arguments rather than relying on globals.
0 commit comments