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.