<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.anunna.wur.nl/index.php?action=history&amp;feed=atom&amp;title=Tutorials%2FParallelism-Estimating-Pi</id>
	<title>Tutorials/Parallelism-Estimating-Pi - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.anunna.wur.nl/index.php?action=history&amp;feed=atom&amp;title=Tutorials%2FParallelism-Estimating-Pi"/>
	<link rel="alternate" type="text/html" href="https://wiki.anunna.wur.nl/index.php?title=Tutorials/Parallelism-Estimating-Pi&amp;action=history"/>
	<updated>2026-07-09T12:29:58Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>https://wiki.anunna.wur.nl/index.php?title=Tutorials/Parallelism-Estimating-Pi&amp;diff=2953&amp;oldid=prev</id>
		<title>Honfi001: Created page with &quot;This tutorial is a gentle, hands-on introduction to &#039;&#039;&#039;parallel computing on Anunna&#039;&#039;&#039;. Instead of a heavy scientific code, it uses one friendly problem — estimating the number π by throwing random darts — and runs the &#039;&#039;same&#039;&#039; calculation across every kind of parallelism the cluster offers: a single core, many threads, many processes, multiple nodes, and a GPU. Because the problem stays the same, you can see exactly what each step up the ladder buys you.  You do no...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.anunna.wur.nl/index.php?title=Tutorials/Parallelism-Estimating-Pi&amp;diff=2953&amp;oldid=prev"/>
		<updated>2026-06-24T08:40:03Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;This tutorial is a gentle, hands-on introduction to &amp;#039;&amp;#039;&amp;#039;parallel computing on Anunna&amp;#039;&amp;#039;&amp;#039;. Instead of a heavy scientific code, it uses one friendly problem — estimating the number π by throwing random darts — and runs the &amp;#039;&amp;#039;same&amp;#039;&amp;#039; calculation across every kind of parallelism the cluster offers: a single core, many threads, many processes, multiple nodes, and a GPU. Because the problem stays the same, you can see exactly what each step up the ladder buys you.  You do no...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;This tutorial is a gentle, hands-on introduction to &amp;#039;&amp;#039;&amp;#039;parallel computing on Anunna&amp;#039;&amp;#039;&amp;#039;. Instead of a heavy scientific code, it uses one friendly problem — estimating the number π by throwing random darts — and runs the &amp;#039;&amp;#039;same&amp;#039;&amp;#039; calculation across every kind of parallelism the cluster offers: a single core, many threads, many processes, multiple nodes, and a GPU. Because the problem stays the same, you can see exactly what each step up the ladder buys you.&lt;br /&gt;
&lt;br /&gt;
You do not need to write any C code. The whole exercise comes down to filling in &amp;#039;&amp;#039;&amp;#039;two&amp;#039;&amp;#039;&amp;#039; blanks in two job scripts, launching a batch of jobs, and reading three plots.&lt;br /&gt;
&lt;br /&gt;
== What you will learn ==&lt;br /&gt;
&lt;br /&gt;
* How a single program can be run serially, with threads (OpenMP), with processes (MPI), across several nodes, and on a GPU.&lt;br /&gt;
* How Slurm &amp;#039;&amp;#039;allocates&amp;#039;&amp;#039; resources and why your program still has to be &amp;#039;&amp;#039;told&amp;#039;&amp;#039; how many to use.&lt;br /&gt;
* The difference between &amp;#039;&amp;#039;&amp;#039;strong scaling&amp;#039;&amp;#039;&amp;#039; (same work, more workers, less time) and &amp;#039;&amp;#039;&amp;#039;weak scaling&amp;#039;&amp;#039;&amp;#039; (more workers, more work, same time).&lt;br /&gt;
* Why more workers make a calculation &amp;#039;&amp;#039;&amp;#039;faster, not more accurate&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
* How to read scaling and speedup plots — and where their limits are.&lt;br /&gt;
&lt;br /&gt;
== Before you begin ==&lt;br /&gt;
&lt;br /&gt;
You should already be comfortable logging into Anunna and submitting a basic Slurm job. If you are not sure, work through the [[Linux Basic/Linux Self Assessment|Linux Self Assessment]] first.&lt;br /&gt;
&lt;br /&gt;
You also need the &amp;#039;&amp;#039;&amp;#039;course environment loaded&amp;#039;&amp;#039;&amp;#039;, because it sets the &amp;lt;code&amp;gt;$myScratch&amp;lt;/code&amp;gt; variable that the tutorial relies on. If your instructor has not told you otherwise, it is loaded for you when you log in. You can check with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
echo &amp;quot;$myScratch&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If that prints a path under &amp;lt;code&amp;gt;/lustre/scratch/...&amp;lt;/code&amp;gt;, you are ready. If it prints nothing, load the course environment before continuing.&lt;br /&gt;
&lt;br /&gt;
Everything you run lives in two places:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;The shared tutorial&amp;#039;&amp;#039;&amp;#039; at &amp;lt;code&amp;gt;/lustre/shared/hpcCourses/pi-tutorial&amp;lt;/code&amp;gt; — read-only, the same for everyone.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Your personal copy&amp;#039;&amp;#039;&amp;#039; at &amp;lt;code&amp;gt;$myScratch/hpcCourse/pi-tutorial&amp;lt;/code&amp;gt; — created the first time you launch, and where you make your edits.&lt;br /&gt;
&lt;br /&gt;
== The big idea: estimating π with random darts ==&lt;br /&gt;
&lt;br /&gt;
Imagine a square dartboard with a quarter-circle drawn inside it. If you throw darts at random and they land anywhere in the square with equal chance, the fraction that land &amp;#039;&amp;#039;inside&amp;#039;&amp;#039; the quarter-circle is exactly the ratio of the two areas — which works out to π/4. So if you throw &amp;#039;&amp;#039;&amp;#039;N&amp;#039;&amp;#039;&amp;#039; darts and &amp;#039;&amp;#039;&amp;#039;H&amp;#039;&amp;#039;&amp;#039; of them land inside:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
π ≈ 4 × H / N&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Throw more darts, and the estimate gets better. This is a &amp;#039;&amp;#039;&amp;#039;Monte Carlo&amp;#039;&amp;#039;&amp;#039; method: a calculation built out of huge numbers of independent random trials. It has one property that makes it perfect for teaching parallelism — every dart is independent, so the work splits cleanly across as many workers as you like, with almost no coordination between them.&lt;br /&gt;
&lt;br /&gt;
The tutorial implements this &amp;#039;&amp;#039;same&amp;#039;&amp;#039; dart-throwing kernel six ways:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Rung !! Program !! What it uses&lt;br /&gt;
|-&lt;br /&gt;
| serial || &amp;lt;code&amp;gt;pi_serial&amp;lt;/code&amp;gt; || one core (the reference)&lt;br /&gt;
|-&lt;br /&gt;
| OpenMP || &amp;lt;code&amp;gt;pi_omp&amp;lt;/code&amp;gt; || many threads on one node&lt;br /&gt;
|-&lt;br /&gt;
| MPI || &amp;lt;code&amp;gt;pi_mpi&amp;lt;/code&amp;gt; || many processes on one node&lt;br /&gt;
|-&lt;br /&gt;
| multi-node || &amp;lt;code&amp;gt;pi_mpi&amp;lt;/code&amp;gt; || processes spread across several nodes (same program)&lt;br /&gt;
|-&lt;br /&gt;
| GPU || &amp;lt;code&amp;gt;pi_gpu&amp;lt;/code&amp;gt; || thousands of device threads on one GPU&lt;br /&gt;
|-&lt;br /&gt;
| hybrid || &amp;lt;code&amp;gt;pi_hybrid&amp;lt;/code&amp;gt; || MPI processes, each running OpenMP threads&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Because the random-number generator is identical everywhere and each worker draws its own independent stream, the &amp;#039;&amp;#039;answer&amp;#039;&amp;#039; for a given number of darts and seed is the same no matter how the work is divided. Only the &amp;#039;&amp;#039;time&amp;#039;&amp;#039; changes. That is the whole point.&lt;br /&gt;
&lt;br /&gt;
== Step 1: Make the tutorial modules available ==&lt;br /&gt;
&lt;br /&gt;
The tutorial ships as a set of environment modules. Point Lmod at them once per session:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
module use /lustre/shared/hpcCourses/pi-tutorial/modules&lt;br /&gt;
module avail pi-tutorial&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will see three modules. They are mutually exclusive — loading one unloads the others:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;pi-tutorial&amp;lt;/code&amp;gt; — the CPU rungs (serial, OpenMP, MPI, hybrid).&lt;br /&gt;
* &amp;lt;code&amp;gt;pi-tutorial-cuda&amp;lt;/code&amp;gt; — the NVIDIA GPU rung.&lt;br /&gt;
* &amp;lt;code&amp;gt;pi-tutorial-rocm&amp;lt;/code&amp;gt; — the AMD GPU rung.&lt;br /&gt;
&lt;br /&gt;
You do not need to load these by hand for the exercise — each job script loads the right one for you. The &amp;lt;code&amp;gt;module use&amp;lt;/code&amp;gt; line above is enough.&lt;br /&gt;
&lt;br /&gt;
== Step 2: Get your personal copy of the scripts ==&lt;br /&gt;
&lt;br /&gt;
Run the launcher once:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
bash /lustre/shared/hpcCourses/pi-tutorial/exercise/launch.sh&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On this first run it does three things: it copies the job scripts into your personal space at &amp;lt;code&amp;gt;$myScratch/hpcCourse/pi-tutorial/slurm&amp;lt;/code&amp;gt;, clears any old results, and submits the whole batch of jobs. Watch them with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
squeue --me&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Most of the jobs (serial, GPU, multi-node, hybrid) are complete and will run correctly. &amp;#039;&amp;#039;&amp;#039;Two of them — the OpenMP and the MPI jobs — contain a deliberate blank&amp;#039;&amp;#039;&amp;#039; and will not give a useful result yet. Fixing those blanks is your job, and it is the next step.&lt;br /&gt;
&lt;br /&gt;
== Step 3: Fill in the two blanks ==&lt;br /&gt;
&lt;br /&gt;
When you ask Slurm for resources, Slurm sets aside the cores or tasks you requested — but it does not automatically tell your &amp;#039;&amp;#039;program&amp;#039;&amp;#039; about them. You have to pass the number along. Slurm makes that easy by exporting the counts as environment variables. Your task is to connect the two.&lt;br /&gt;
&lt;br /&gt;
Open the OpenMP script in your personal copy (use &amp;lt;code&amp;gt;nano&amp;lt;/code&amp;gt; if you are not comfortable with &amp;lt;code&amp;gt;vi&amp;lt;/code&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nano $myScratch/hpcCourse/pi-tutorial/slurm/run_multithread.slurm&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will find this line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
export OMP_NUM_THREADS=FIXME&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script asked Slurm for cores with &amp;lt;code&amp;gt;--cpus-per-task&amp;lt;/code&amp;gt;, and Slurm exported that count as &amp;lt;code&amp;gt;SLURM_CPUS_PER_TASK&amp;lt;/code&amp;gt;. Tell OpenMP to use exactly that many threads:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
export OMP_NUM_THREADS=${SLURM_CPUS_PER_TASK}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now the MPI script:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nano $myScratch/hpcCourse/pi-tutorial/slurm/run_multiprocess.slurm&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here the blank is the number of processes to launch:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
mpirun -np FIXME pi_mpi --total 1e10 --seed 12345&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This script asked Slurm for tasks with &amp;lt;code&amp;gt;--ntasks&amp;lt;/code&amp;gt;, and Slurm exported that as &amp;lt;code&amp;gt;SLURM_NTASKS&amp;lt;/code&amp;gt;. Launch one process per task:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
mpirun -np ${SLURM_NTASKS} pi_mpi --total 1e10 --seed 12345&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is the entire editable surface — two lines. Save both files.&lt;br /&gt;
&lt;br /&gt;
== Step 4: Launch the full sweep ==&lt;br /&gt;
&lt;br /&gt;
Run the launcher again:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
bash /lustre/shared/hpcCourses/pi-tutorial/exercise/launch.sh&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This time it &amp;#039;&amp;#039;&amp;#039;keeps your edited scripts&amp;#039;&amp;#039;&amp;#039; (it never overwrites your copy), clears the old results, and resubmits everything. The launcher sweeps a range of sizes for you automatically — OpenMP from 1 up to 128 threads, MPI from 1 up to 128 processes — plus the GPU, multi-node, and hybrid runs.&lt;br /&gt;
&lt;br /&gt;
Keep an eye on the queue:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
squeue --me&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each finished job writes one result line into &amp;lt;code&amp;gt;$myScratch/hpcCourse/pi-tutorial/results&amp;lt;/code&amp;gt;. Wait until the queue is empty before moving on.&lt;br /&gt;
&lt;br /&gt;
== Step 5: Analyze your results ==&lt;br /&gt;
&lt;br /&gt;
The analysis script reads every result file, prints a summary table, and writes three plots. It needs matplotlib, which lives in the SciPy bundle:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
module load 2024 SciPy-bundle/2024a&lt;br /&gt;
python /lustre/shared/hpcCourses/pi-tutorial/analyze.py $myScratch/hpcCourse/pi-tutorial/results&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When it finishes you will have three PNG files in your results directory:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;walltime_vs_cores.png&amp;lt;/code&amp;gt; — how long each run took, against the number of cores.&lt;br /&gt;
* &amp;lt;code&amp;gt;speedup_vs_cores.png&amp;lt;/code&amp;gt; — how much faster than the serial run, against the ideal straight line.&lt;br /&gt;
* &amp;lt;code&amp;gt;best_per_approach.png&amp;lt;/code&amp;gt; — the single fastest run of each approach, side by side.&lt;br /&gt;
&lt;br /&gt;
Copy them to your laptop (for example with &amp;lt;code&amp;gt;scp&amp;lt;/code&amp;gt;) to view them, or open them through the OnDemand file browser.&lt;br /&gt;
&lt;br /&gt;
== Results ==&lt;br /&gt;
&lt;br /&gt;
[[File:Pi-tutorial-walltime-vs-cores.png|thumb|right|360px|Wall time falls as cores are added. Note both axes are logarithmic; a straight line here means near-ideal scaling.]]&lt;br /&gt;
[[File:Pi-tutorial-speedup-vs-cores.png|thumb|right|360px|Speedup against the serial baseline. The dashed line is perfect (linear) speedup; real runs sit just below it.]]&lt;br /&gt;
[[File:Pi-tutorial-best-per-approach.png|thumb|right|360px|The fastest run of each approach. The GPU finishes the same 10-billion-dart job in a fraction of the time the CPU rungs need.]]&lt;br /&gt;
&lt;br /&gt;
A correct run produces a table like the one below (trimmed; your exact times will vary by a few percent). Every run threw the same &amp;#039;&amp;#039;&amp;#039;10 billion&amp;#039;&amp;#039;&amp;#039; darts.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
variant   nodes cores              device       wall_s            pi   pct_err&lt;br /&gt;
serial        1     1                 cpu       26.008     3.1416052   0.00040&lt;br /&gt;
omp           1     1                 cpu       26.499     3.1416052   0.00040&lt;br /&gt;
omp           1    16                 cpu        1.663     3.1415960   0.00011&lt;br /&gt;
omp           1   128                 cpu        0.235     3.1416075   0.00047&lt;br /&gt;
mpi           1     1                 cpu       69.676     3.1416052   0.00040&lt;br /&gt;
mpi           1   128                 cpu        0.555     3.1416075   0.00047&lt;br /&gt;
mpi           4    64                 cpu        1.090     3.1416039   0.00036&lt;br /&gt;
hybrid        4   128                 cpu        0.217     3.1416075   0.00047&lt;br /&gt;
gpu           1     1  AMD_Instinct_MI210        0.080     3.1415897   0.00009&lt;br /&gt;
gpu           1     1  NVIDIA_A100_80GB_PCIe     0.045     3.1415897   0.00009&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Two things are worth noticing straight away:&lt;br /&gt;
&lt;br /&gt;
* The serial run took about &amp;#039;&amp;#039;&amp;#039;26 seconds&amp;#039;&amp;#039;&amp;#039;; the 128-thread OpenMP run took about &amp;#039;&amp;#039;&amp;#039;0.24 seconds&amp;#039;&amp;#039;&amp;#039;; the A100 GPU finished in about &amp;#039;&amp;#039;&amp;#039;0.045 seconds&amp;#039;&amp;#039;&amp;#039;. The work was identical — only the time changed.&lt;br /&gt;
* Look at the &amp;lt;code&amp;gt;pi&amp;lt;/code&amp;gt; column. For any given total work and seed, the estimate is &amp;#039;&amp;#039;&amp;#039;identical&amp;#039;&amp;#039;&amp;#039; across serial, OpenMP, MPI, multi-node, and hybrid — for example every 128-core CPU run lands on 3.1416075. Splitting the darts across more workers did not change the answer, because each worker draws its own independent random stream. (The GPU differs in the last few digits because it uses far more streams; it still lands well within the expected error.)&lt;br /&gt;
&lt;br /&gt;
== Discussion ==&lt;br /&gt;
&lt;br /&gt;
The plots tell a tidy story: add workers, get proportionally faster, almost along the ideal line. That is real — but it is important to understand &amp;#039;&amp;#039;why&amp;#039;&amp;#039; it looks so clean, and where the limits are.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;This problem is unusually friendly.&amp;#039;&amp;#039;&amp;#039; Throwing darts is &amp;quot;embarrassingly parallel&amp;quot;: the workers never need to talk to each other until the very end, when their hit-counts are summed. Most real scientific codes are not like this. A fluid-dynamics solver, a molecular-dynamics simulation, or a genome assembler has data dependencies and communication between workers, and that communication eventually caps how much faster you can go (this ceiling is known as &amp;#039;&amp;#039;&amp;#039;Amdahl&amp;#039;s law&amp;#039;&amp;#039;&amp;#039;). Do not expect your own code to scale as cleanly as π does — this tutorial shows you the &amp;#039;&amp;#039;best&amp;#039;&amp;#039; case, not the typical one.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;More workers buy speed, not accuracy.&amp;#039;&amp;#039;&amp;#039; The error of a Monte Carlo estimate shrinks with the number of darts as roughly 1/√N — so to halve the error you must throw &amp;#039;&amp;#039;&amp;#039;four times&amp;#039;&amp;#039;&amp;#039; as many darts. Adding cores lets you throw a fixed number of darts &amp;#039;&amp;#039;sooner&amp;#039;&amp;#039;, or throw &amp;#039;&amp;#039;more&amp;#039;&amp;#039; darts in the same time, but it never makes a fixed number of darts more accurate. Speed and accuracy are separate knobs.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Compare equal core counts.&amp;#039;&amp;#039;&amp;#039; In the table, the 4-node MPI run (1.09 s) looks &amp;#039;&amp;#039;slower&amp;#039;&amp;#039; than the 1-node MPI run (0.56 s). It is not a penalty for crossing nodes — the 4-node run used &amp;#039;&amp;#039;&amp;#039;64&amp;#039;&amp;#039;&amp;#039; cores while the 1-node run used &amp;#039;&amp;#039;&amp;#039;128&amp;#039;&amp;#039;&amp;#039;. When you compare like with like (64 cores on 1 node ≈ 1.11 s versus 64 cores across 4 nodes ≈ 1.09 s) the times are essentially equal: for this workload the network between nodes is not a bottleneck. Always check the core count before reading a comparison as good or bad news.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;The measurements are deliberately rough.&amp;#039;&amp;#039;&amp;#039; Each point is a single run with a single seed, and the fastest runs finish in well under a second — far too short to time precisely on a shared machine. That noise is why a step in the curve can occasionally look &amp;#039;&amp;#039;better&amp;#039;&amp;#039; than perfect (&amp;quot;super-linear&amp;quot;); it is timing jitter, not magic. A proper benchmark repeats each run several times and reports the median. This tutorial trades that rigour for clarity and a short queue.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;A real-world wrinkle.&amp;#039;&amp;#039;&amp;#039; You may notice the single-process MPI run (about 70 s) is markedly slower than the single-thread OpenMP run (about 26 s) for the same work — and that gap persists across the sweep. That is a property of how this particular MPI program starts up and is being investigated; it is not a general rule that MPI is slower than OpenMP. It is a good reminder that &amp;#039;&amp;#039;how&amp;#039;&amp;#039; you measure matters as much as &amp;#039;&amp;#039;what&amp;#039;&amp;#039; you measure.&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
&lt;br /&gt;
You ran one calculation on a single core and on hundreds, on CPUs and on GPUs, on one node and across four — and the answer never changed, only the time. That is the core lesson of parallel computing on a cluster: &amp;#039;&amp;#039;&amp;#039;you choose a parallel model to match the structure of your problem&amp;#039;&amp;#039;&amp;#039;, and the reward is speed, not correctness.&lt;br /&gt;
&lt;br /&gt;
A few takeaways to carry forward:&lt;br /&gt;
&lt;br /&gt;
* If your work is made of independent pieces (parameter sweeps, many input files, Monte Carlo trials), it will parallelise well — threads, processes, or a GPU all help.&lt;br /&gt;
* If your work has steps that depend on each other, expect communication to limit your speedup, and measure before assuming more cores will help.&lt;br /&gt;
* More cores make things faster; only more samples (or a better method) make a Monte Carlo result more accurate.&lt;br /&gt;
&lt;br /&gt;
To go further, try editing a script to use &amp;lt;code&amp;gt;--per-worker&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;--total&amp;lt;/code&amp;gt; and watch the difference between weak and strong scaling, or move on to packaging your own software with the [[Tutorials/Apptainer-Introduction|Apptainer tutorials]].&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;&amp;lt;code&amp;gt;myScratch is not set&amp;lt;/code&amp;gt;&amp;#039;&amp;#039;&amp;#039; — the course environment is not loaded. Load it and try again.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;A GPU job sits in the queue (pending)&amp;#039;&amp;#039;&amp;#039; — the NVIDIA cards may be busy; the job will start when one frees up. The AMD GPU run uses a reservation and is the reliable one for the course.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;You edited a script badly and want a clean copy&amp;#039;&amp;#039;&amp;#039; — delete your script folder and re-run the launcher to get fresh copies:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
rm -rf &amp;quot;$myScratch/hpcCourse/pi-tutorial/slurm&amp;quot;&lt;br /&gt;
bash /lustre/shared/hpcCourses/pi-tutorial/exercise/launch.sh&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Linux Basic/Linux Self Assessment|Linux Self Assessment]] — check your command-line readiness.&lt;br /&gt;
* [[Tutorials/Apptainer-Introduction|Apptainer: Introduction]] — packaging software for reproducible runs.&lt;br /&gt;
* [[Tutorials|All tutorials]] · [[Workshops]] · [[Training Materials]]&lt;/div&gt;</summary>
		<author><name>Honfi001</name></author>
	</entry>
</feed>