Benchmark How Long a Program Runs In Linux Using Bash

The following bash code might come in handy if you want to benchmark how long a program runs. The example assumes you want to pass two parameters along to the program. A timestamp is captured at the start and end of execution, and the difference (the processing time) is printed in seconds.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
##################################################
# Benchmark the processing time when a program
# executes with two required parameters: parameter1 parameter2
##################################################

if [[ $1 = "" || $2 = "" ]]; then
        echo "Usage: `basename $0` parameter1 parameter2"
else
        _start_time=`date +%s`
        _parameter1=$1
        _parameter2=$2
        ### YOUR COMMAND HERE WITH parameter1 AND parameter2 ###
        _end_time=`date +%s`
        _processing_time=$((_end_time-_start_time))
        echo "Source File: $_parameter1"
        echo "Destination File: $_parameter2"
        echo "Start time: $_start_time"
        echo "End time: $_end_time"
        echo "Processing time is: $_processing_time"
fi

A few useful additions.

One thing to know about the script above: the ### YOUR COMMAND HERE ### line is just a comment — bash doesn’t run anything. If you copy-paste and try it, you’ll get Processing time is: 0 because the timer fires at _start_time, immediately advances to _end_time, and there’s nothing in between. Replace that comment with the actual command you want to measure, e.g.:

1
rsync -a "$_parameter1" "$_parameter2"

The simpler answer: time. If “how long did this take?” is the only question you’re asking, bash already has a builtin for it:

1
2
3
4
time ./myprogram param1 param2
# real    0m1.234s
# user    0m0.045s
# sys     0m0.012s

The three numbers are: wall-clock time (real), CPU time spent in user-mode code (user), and CPU time spent in kernel calls (sys). When user + sys is much less than real, your program was waiting on something (disk, network, sleep). When they’re roughly equal, it was CPU-bound.

The time keyword also wraps a whole pipeline or compound command:

1
time { ./step1 && ./step2 | tee out.log; }

The richer answer: /usr/bin/time. The standalone GNU time binary (different program, same name — note the explicit path or you’ll get the bash builtin) reports a lot more:

1
2
3
4
5
6
7
8
/usr/bin/time -v ./myprogram param1 param2
# Command being timed: "./myprogram"
# Maximum resident set size (kbytes): 12480
# Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.23
# Percent of CPU this job got: 87%
# Major (requiring I/O) page faults: 0
# Minor (reclaiming a frame) page faults: 1532
# ...

That’s the one to reach for when you care about memory usage, page faults, or context switches alongside timing. Some distros don’t ship it by default — apt install time or dnf install time if it’s missing.

Sub-second timing without date +%s. The original script’s resolution is one second, which is too coarse for anything fast. A few ways to get finer numbers:

1
2
3
4
5
6
7
8
9
10
11
# Nanosecond resolution via date (still spawns a subprocess each call)
_start=$(date +%s%N)
# ... work ...
_end=$(date +%s%N)
echo "$(( (_end - _start) / 1000000 )) ms"

# Bash 5+ has $EPOCHREALTIME — sub-second, no subprocess at all
_start=$EPOCHREALTIME
# ... work ...
_end=$EPOCHREALTIME
awk -v s="$_start" -v e="$_end" 'BEGIN { printf "%.3f s\n", e - s }'

$EPOCHREALTIME is a string like 1715000123.456789 with microsecond resolution. It’s free — no fork, no exec — so it’s safe to call inside a hot loop. The catch: bash arithmetic is integer-only, so you need awk or bc to do the floating-point subtraction.

When you want statistical confidence: hyperfine. If the question is “is my new version actually faster?” — you want averages over many runs, with warmup, and ideally a confidence interval. hyperfine is the modern tool for this:

1
2
3
4
5
6
hyperfine --warmup 3 --runs 20 \
    './build_old.sh input.txt' \
    './build_new.sh input.txt'
# Summary
#   './build_new.sh input.txt' ran
#     1.42 ± 0.03 times faster than './build_old.sh input.txt'

It runs each command repeatedly, discards the warmup runs (which are usually slower because of cold caches), reports mean ± standard deviation, and gives you a relative-speedup figure. If you’re benchmarking anything where one-off variance matters — and that’s most things — this beats running time three times and squinting at the numbers.

Two gotchas worth knowing.

  • System clock changes break date +%s. If NTP slews the clock backwards mid-run, your _processing_time can be negative or inflated. $EPOCHREALTIME has the same problem (both read CLOCK_REALTIME). For benchmarking, the correct clock is CLOCK_MONOTONIC, which never goes backwards — but bash doesn’t expose it directly. Use time or hyperfine for anything that needs to survive a clock change.
  • Disk caches make repeated runs misleadingly fast. The first run of a program reads cold data from disk; the second run hits the page cache and looks 10x faster. Either drop caches between runs (echo 3 | sudo tee /proc/sys/vm/drop_caches on Linux) or — easier — let hyperfine handle the warmup phase for you.
This entry was posted in Linux, Operating System, Ubuntu. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *


5 − = four