Skip to content

Commit 7fce1f2

Browse files
mmckyclaude
andauthored
[numba.md] numba_ex3: run the solution at the n the exercise asks for (#601)
* [numba.md] numba_ex3: run the solution at the n the exercise asks for The statement says to use a substantial sample size such as n = 100_000_000, but the solution reused the shared 10^6 arrays --- at that size both timed cells display as 0.00 seconds, demonstrating nothing. The solution now draws its own 10^8 points (with a memory note), times the parallel version, and compares against speed_ex1's serial jitted function on the same arrays so the multithreading gain is visible on the page. The shared 10^6 arrays are unchanged: speed_ex1's pure-Python comparison would take minutes at 10^8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * @mmcky edit of lecture wording * [numba.md] numba_ex3: release the 1e8 arrays after the timings The `u_big`/`v_big` pair added for numba_ex3 stayed alive for the rest of the notebook. Because `numba_ex_draw_speed` rebinds `u_draws`/`v_draws` to a second pair of 1e8-element arrays, peak memory reached ~3.2 GB rather than ~1.6 GB. That is comfortable on the g4dn.2xlarge CI runner but not on the smaller machines the accompanying memory note is written for. Delete the arrays after their last use, and fix "Lets" -> "Let's" plus the trailing whitespace introduced alongside it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [numba.md] numba_ex3: align the prose with the measured timings The Netlify preview of 6246b07 reports 0.1448s serial against 0.0353s parallel at n=100_000_000, a 4.1x gain, but the solution claimed "around 3x on our workstation". Drop the multiple rather than restate it: these lectures re-execute every build on a shared runner, so a pinned figure drifts, and line 765 was the only hardcoded speedup in the file — the rest describes gains qualitatively and lets the printed timings carry the fact. The exercise preamble still warned "you should not expect huge gains here", which was written for the old small-n setup and now contradicts the near-linear scaling the solution demonstrates. Reframe it around giving each thread enough work, which is why the exercise asks for a large n. Prose only, so the execution cache for the code cells is unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent d35eb83 commit 7fce1f2

1 file changed

Lines changed: 51 additions & 21 deletions

File tree

lectures/numba.md

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -687,19 +687,20 @@ effort to compute the constant $\pi$ by Monte Carlo.
687687
688688
Now try adding parallelization and see if you get further speed gains.
689689
690-
You should not expect huge gains here because, while there are many
691-
independent tasks (draw point and test if in circle), each one has low
692-
execution time.
690+
There are many independent tasks here (draw a point and test whether it falls
691+
in the circle), but each one has very low execution time.
693692
694-
Generally speaking, parallelization is less effective when the individual
695-
tasks to be parallelized are very small relative to total execution time.
693+
Generally speaking, parallelization is less effective when the individual tasks
694+
are very small relative to the overheads of spreading them across multiple CPUs.
696695
697-
This is due to overheads associated with spreading all of these small tasks across multiple CPUs.
696+
The way around this is to give each thread enough work to make those overheads
697+
worth paying.
698698
699-
Nevertheless, with suitable hardware, it is possible to get nontrivial speed gains in this exercise.
699+
So, for the size of the Monte Carlo simulation, use something substantial, such
700+
as `n = 100_000_000`.
700701
701-
For the size of the Monte Carlo simulation, use something substantial, such as
702-
`n = 100_000_000`.
702+
At that scale, and with suitable hardware, you should see a clear gain over the
703+
serial version.
703704
```
704705

705706
```{solution-start} numba_ex3
@@ -723,36 +724,65 @@ def calculate_pi_parallel(u_draws, v_draws):
723724
return area_estimate * 4 # dividing by radius**2
724725
```
725726

726-
Now let's see how fast it runs:
727+
Parallelization pays off when each thread has enough work to overcome the overhead costs while
728+
breaking the problem into parts for work to happen simultaneously.
729+
730+
Let's draw a fresh, much larger set of points rather than reusing the arrays from above.
731+
732+
```{note}
733+
The two arrays below occupy about 1.6 GB of memory — reduce `n` if your
734+
machine is short on RAM.
735+
```
736+
737+
```{code-cell} ipython3
738+
n = 100_000_000
739+
rng = np.random.default_rng()
740+
u_big = rng.uniform(size=n)
741+
v_big = rng.uniform(size=n)
742+
```
743+
744+
Now let's see how fast it runs (the second call measures runtime without
745+
compilation time):
727746

728747
```{code-cell} ipython3
729748
with qe.Timer():
730-
calculate_pi_parallel(u_draws, v_draws)
749+
calculate_pi_parallel(u_big, v_big)
731750
```
732751

733752
```{code-cell} ipython3
734753
with qe.Timer():
735-
calculate_pi_parallel(u_draws, v_draws)
754+
calculate_pi_parallel(u_big, v_big)
736755
```
737756

738-
By switching parallelization on and off (selecting `True` or
739-
`False` in the `@jit` annotation), we can test the speed gain that
740-
multithreading provides on top of JIT compilation.
757+
For comparison, here is the serial jitted version from {ref}`speed_ex1` on the
758+
same points:
759+
760+
```{code-cell} ipython3
761+
with qe.Timer():
762+
calculate_pi(u_big, v_big)
763+
```
741764

742-
On our workstation, we find that parallelization provides a modest but
743-
worthwhile speed gain here.
765+
Comparing the last two timings, multithreading provides a substantial speed
766+
gain on top of JIT compilation.
744767

745768
(If you are executing locally, you will get different results, depending mainly
746-
on the number of CPUs on your machine.)
769+
on the number of CPUs on your machine — and at small sample sizes the
770+
parallel version can even be slower, because the gains cannot cover the cost of
771+
distributing work across threads.)
772+
773+
These two arrays are large and we are finished with them, so we release the
774+
memory before moving on.
775+
776+
```{code-cell} ipython3
777+
del u_big, v_big
778+
```
747779

748780
Notice that we drew all of the random points *before* the loop and passed them in
749781
as arrays, so the parallel loop only *reads* from memory.
750782

751783
Drawing the points *inside* the parallel loop instead is surprisingly delicate.
752784

753-
754-
We investigate why, and how to do it safely, in
755-
{ref}`numba_ex_race`.
785+
We investigate why, and how to do it safely, in {ref}`numba_ex_race`.
756786

757787
```{solution-end}
758788
```

0 commit comments

Comments
 (0)