Section 1 of 15

1 Before you start

Before you begin, take a few minutes to check what you already know and how confident you feel. You will see the same questions again at the end of the module — this helps both you and us measure what you have learned. Click an option for every question and confidence rating, then click Next to continue.

Pre-test

What does the -P 8 flag do in xargs -P 8 -I {} cmd {}?

Pre-test

In GNU parallel, what does the {} placeholder stand for?

Pre-test

For an input file ecg/P-001.dat, what does the parallel placeholder {/.} produce?

Pre-test

Why use parallel -k?

Pre-test

Which situation is NOT safe to parallelise as-is?

Pre-test

Why does a plain serial for loop over independent files leave most of a multi-core machine idle?

Pre-test

You must parallelise a job over files whose names contain spaces. Which pairing handles this safely?

Pre-test

Your conversion script loads a 2 GB file into RAM on an 8-core, 8 GB machine. What does the lesson advise?

Pre-confidence

I can explain why a plain for loop uses only one CPU core.

Not at all confident
Fully confident
Pre-confidence

I can run a command on many files at once with xargs -P or GNU parallel.

Not at all confident
Fully confident
Pre-confidence

I know why writing to one shared file from many parallel jobs is dangerous.

Not at all confident
Fully confident
Section 2 of 15

2 Introduction

You now know how to find files and run a command on each one. The next question is speed. When you have 500 patient files to compress, or a Quality Control (QC) script to run on 60 cohort chunks, doing them one at a time wastes most of your computer's power. Every modern laptop has several CPU cores, but a plain for loop uses exactly one.

This lesson is about putting the rest of your machine to work. You will learn:

  • Why a serial for loop leaves most of your machine idle.
  • xargs -P - the parallel flag you already know.
  • GNU parallel - a friendlier tool built for exactly this job.
  • The traps of parallel writes and how to keep your output straight.
A serial for loop runs every file through a single CPU core while the others sit idle, whereas parallel execution spreads the same work across all cores and finishes in roughly a quarter of the time.
A serial for loop runs every file through a single CPU core while the others sit idle, whereas parallel execution spreads the same work across all cores and finishes in roughly a quarter of the time.
Section 3 of 15

3 The Problem - One Core at a Time

Say you have a folder of per-patient vitals files and a slow conversion step. A normal for loop runs them strictly in order i.e. file 2 does not start until file 1 is completely finished.

Try it on the eight vitals files. We'll stand in for the slow conversion step with sleep 1, and time how long the whole loop takes:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
time for f in vitals/*.csv
do
 sleep 1
done

Eight files, one second each, back to back: about eight seconds. Image a grocery store with eight checkout lanes open but only one cashier working, while everyone queues in that single line. The other seven lanes sit empty, and those empty lanes are your other seven CPU cores, idle the whole time.

A sequential loop over independent files runs on a single CPU core while the rest stay idle, so distributing the work across all cores cuts wall-clock time by roughly the core count.
A sequential loop over independent files runs on a single CPU core while the rest stay idle, so distributing the work across all cores cuts wall-clock time by roughly the core count.

Because each file is independent and doesn't rely on the others, this work is "embarrassingly parallel." This is the ideal case for running several jobs at once.

First, before we do parallelization, let's find out how many cores you have. The nproc command prints your available CPU count:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
nproc

Hold onto that number. This is the number of your CPU count.

Section 4 of 15

4 xargs -P - The Parallel Flag

You met xargs in the finding-files lesson: it takes a list of inputs and builds command lines from it. If you add the -P N flag, xargs will run up to N of those commands at the same time.

Here is the shape of it. This counts the lines in every vitals file, two at a time:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
ls vitals/*.csv | xargs -P 2 -I {} wc -l {}

How to read this: ls lists the files, the pipe hands that list to xargs, -I {} means "drop each filename where the {} is", and -P 2 means "keep 2 of these running at once". The reason why you are using 2 is because you would only have 2 CPUs loaded as per your nproc command just now.

Adding -P N to xargs runs up to N jobs at once, so the same set of files finishes in roughly 1/N of the time on an N-core machine.
Adding -P N to xargs runs up to N jobs at once, so the same set of files finishes in roughly 1/N of the time on an N-core machine.

So back to the eight one-second jobs. The serial loop took about eight seconds: one lane, one job at a time. Now hand the same work to xargs and open a second lane:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
time ls vitals/*.csv | xargs -P 2 -I {} sleep 1

This finishes in roughly half the time: about five seconds instead of eight. Two lanes clear the eight jobs in four rounds instead of eight.

The point is the shape of it: one lane gave you eight seconds, two lanes give you about half that.

Section 4.1 of 15

4.1 Safe with find -print0

The same whitespace caution from the find lesson applies here. Filenames with spaces will break your command unless you pair find -print0 with xargs -0:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
find vitals -name '*.csv' -print0 | xargs -0 -P 2 -I {} wc -l {}

A good default for -P is the number of cores your machine has. You can also use -P 0, which tells xargs to run as many jobs as possible at once. This is great for tiny, fast jobs, but risky for memory-hungry scripts.

The -P flag sets how many jobs xargs runs at once, so a batch of N files clears in roughly N divided by -P rounds instead of one file at a time.
The -P flag sets how many jobs xargs runs at once, so a batch of N files clears in roughly N divided by -P rounds instead of one file at a time.
Section 5 of 15

5 GNU parallel - Built For The Job

While xargs -P is great becomes it comes pre-installed on almost every system, GNU parallel is a dedicated tool built exactly for this job. It is friendlier, smarter, and safer.

Note: GNU parallel is not installed in this course's in-browser scratchpad, so the parallel examples in this section are for reading and for running on a real machine. The runnable Try-it cards in this lesson all use xargs -P, which is always available.

Here is how the same command looks using parallel:

$ find ecg -name '*.dat' | parallel convert_ecg {}

What makes GNU parallel good?

It's smart: You don't need to specify -P. It automatically detects how many CPU cores you have and uses them efficiently.

It has progress bars: Add the --bar or --eta flag, and it will give you a beautiful visual progress bar so you know exactly how long your 500 patient files will take.

It handles spaces better: It generally handles weird characters in filenames more gracefully out-of-the-box than xargs.

Section 5.1 of 15

5.1 Feeding files in

There are a few different ways to hand your list of files over to parallel.

You can use three colons (:::) to pass arguments directly on the command line. The shell expands the *.dat into a list of files, and parallel runs one job per file.

$ parallel convert_ecg ::: ecg/*.dat

If you have a massive list of files saved in a text document, use four colons (::::) to read directly from that file.

$ parallel convert_ecg :::: patient_files.txt

Just like xargs, you can simply pipe (|) a list of files directly into parallel.

$ ls ecg/*.dat | parallel convert_ecg
GNU parallel runs the same jobs across the same workers no matter whether the file list arrives as inline arguments (:::), from a file (::::), or piped in (|).
GNU parallel runs the same jobs across the same workers no matter whether the file list arrives as inline arguments (:::), from a file (::::), or piped in (|).
Section 5.2 of 15

5.2 Handy placeholders

One of the best features of GNU parallel is its built-in text manipulation. Instead of writing complex bash string-replacements, parallel uses simple placeholders.

Here is how they alter an input file named ecg/P-001.dat:

  • {} - the whole item (e.g. ecg/P-001.dat).
  • {.} - the item with its extension removed (ecg/P-001).
  • {/} - just the filename, no folder (P-001.dat).
  • {/.} - filename, no folder and no extension (P-001).
  • {#} - the job number.

By combining these placeholders, you can read an input file from one folder and write the output to a completely different folder with a new extension.

GNU parallel feeds each input file through one command, and the five placeholders transform every file in step, with {} keeping the whole path, {.} dropping the extension, {/} dropping the folder, {/.} dropping both, and {#} giving the per-file job number.
GNU parallel feeds each input file through one command, and the five placeholders transform every file in step, with {} keeping the whole path, {.} dropping the extension, {/} dropping the folder, {/.} dropping both, and {#} giving the per-file job number.
$ parallel convert_ecg {} -o results/{/.}.csv ::: ecg/*.dat

What this says is: Read ecg/P-001.dat, and writes to results/P-001.csv. This single line—reading an input, writing a matching output, running one job per core—is the most useful parallel pattern you will use on cohort data.

For example, echoing each filename without its folder or extension:

$ parallel echo {/.} ::: ecg/P-001.dat ecg/P-002.dat
Section 6 of 15

6 Race conditions

Running jobs in parallel is amazing, but it introduces a new danger. When multiple jobs run at the exact same time, their output can overlap, interleave, and become completely unreadable gibberish.

You have to manage this traffic, whether the jobs are printing text to your screen or writing data to a file.

Section 6.1 of 15

6.1 Terminal output

If your script prints results directly to the terminal, a basic xargs -P command will tangle half of job 1's output with job 4's output. GNU parallel fixes this automatically: it is smart enough to group the output. It waits until a job finishes, then prints its output all at once so it remains readable.

By default, parallel prints jobs as they finish. If you need them printed in the exact order they were submitted (as if the loop had been serial), use the -k flag:

$ parallel -k "echo start {}; sleep 1; echo end {}" ::: 1 2 3
GNU parallel buffers each job's output and releases it as one contiguous block when the job finishes, so concurrent jobs stay readable instead of interleaving line by line the way xargs -P does.
GNU parallel buffers each job's output and releases it as one contiguous block when the job finishes, so concurrent jobs stay readable instead of interleaving line by line the way xargs -P does.
Section 6.2 of 15

6.2 File output (Race conditions)

Imagine you have 8 scripts running at once, and they are all trying to write error messages to the same results.log file at the exact same millisecond. The text will overlap, scramble, and become completely unreadable gibberish. This is called Race Conditions.

To avoid this, never write to the same file. Have each job write to its own unique log file. You can do this by using the {} placeholder in your output redirection:

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
ls vitals/*.csv | xargs -P 4 -I {} sh -c 'wc -l "$1" > "$1.log"' _ {}
Parallel processes writing to one shared file corrupt each other's output, so each job must redirect to its own unique file.
Parallel processes writing to one shared file corrupt each other's output, so each job must redirect to its own unique file.
Section 6.3 of 15

6.3 Other safety and visual flags

When managing large batches of output, GNU parallel gives you two incredibly helpful tools:

  • The Progress Bar (--bar): Gives you a visual progress bar and an estimated time of completion for long runs.
  • The Lifesaver (--dry-run): Prints the commands parallel would run, without actually running them. Always use this to double-check your syntax before launching hundreds of jobs!
GNU parallel's --dry-run prints the exact commands it would execute so you can catch a mistake before it repeats across hundreds of jobs, and --bar then monitors the real run with a live progress bar, ETA, and job counter.
GNU parallel's --dry-run prints the exact commands it would execute so you can catch a mistake before it repeats across hundreds of jobs, and --bar then monitors the real run with a live progress bar, ETA, and job counter.
Section 7 of 15

7 When NOT To Parallelise

Parallel is not always safe or faster. Watch for these:

  • Shared output file - ten jobs all appending to one results.csv will interleave and corrupt it. Write one output file per job (the {/.} pattern), then combine afterwards.
  • Order matters (Dependencies) - if Step 2 strictly depends on the result of Step 1, the work is not independent. You cannot build the roof of a house before the walls are finished. These tasks must stay serial.
  • Disk-bound, not CPU-bound - If your script is mostly just copying or moving large files, your CPU isn't doing the hard work—your hard drive is. This is called thrashing and running it in parallel will actually make it slower.
  • Memory (RAM) - Say you have an 8-core machine with 8 GB of RAM. If you run 8 copies of a script, and each copy loads a 2 GB file into memory, you suddenly need 16 GB of RAM. Your computer will freeze or crash. Lower the number of jobs until it safely fits in your memory using -j (e.g., -j 2).
More jobs only help when the work is independent, CPU-bound, and fits in RAM; otherwise extra parallelism corrupts shared output, breaks dependency order, thrashes the disk, or exhausts memory.
More jobs only help when the work is independent, CPU-bound, and fits in RAM; otherwise extra parallelism corrupts shared output, breaks dependency order, thrashes the disk, or exhausts memory.
Section 8 of 15

8 Check your understanding

You have reached the end of the module. Try the same questions again — your answers here, paired with your pre-test answers, are how we measure what the module taught you. Answer every question and confidence rating, then click Submit and see results to view your score.

Post-test

What does the -P 8 flag do in xargs -P 8 -I {} cmd {}?

Post-test

In GNU parallel, what does the {} placeholder stand for?

Post-test

For an input file ecg/P-001.dat, what does the parallel placeholder {/.} produce?

Post-test

Why use parallel -k?

Post-test

Which situation is NOT safe to parallelise as-is?

Post-test

Why does a plain serial for loop over independent files leave most of a multi-core machine idle?

Post-test

You must parallelise a job over files whose names contain spaces. Which pairing handles this safely?

Post-test

Your conversion script loads a 2 GB file into RAM on an 8-core, 8 GB machine. What does the lesson advise?

Post-confidence

I can explain why a plain for loop uses only one CPU core.

Not at all confident
Fully confident
Post-confidence

I can run a command on many files at once with xargs -P or GNU parallel.

Not at all confident
Fully confident
Post-confidence

I know why writing to one shared file from many parallel jobs is dangerous.

Not at all confident
Fully confident
Section 9 of 15

9 Your results

Here is how your post-test answers compare with your pre-test answers. The pre/post pairing is the most reliable way to see what this module actually taught you.

Your score

Submit the post-test to see your results.

Muddiest point

What is the one thing from this module that is still unclear to you?

Rate this module

Overall, how would you rate this module?

How likely are you to recommend this module to a peer? (0 = not at all, 10 = extremely likely)