Skip to content
Bash Profiler and Performance
step 1/4

Reading — step 1 of 4

Learn

~1 min readPerformance and Tools

Bash is not fast. Even simple loops with subshells can take seconds where C would take milliseconds. Knowing what's slow saves hours.

The big rule: avoid forking. Every pipe stage, every $(), every external command is a fork.

Slow patterns:

# BAD — forks for every iteration
for i in $(seq 1 1000); do
    count=$(echo "$i" | wc -c)
done

# GOOD — pure bash
for (( i = 1; i <= 1000; i++ )); do
    count=${#i}
done

Replacements for common slow patterns:

# String length
${#var}                  # not: echo "$var" | wc -c

# Substring
${var:5:10}              # not: echo "$var" | cut -c 6-15

# Replace
${var//foo/bar}          # not: echo "$var" | sed 's/foo/bar/g'

# Split on delimiter
IFS=':' read -ra parts <<< "$path"   # not: cut -d: -f1 ...

# Test condition
[[ -f "$f" ]]            # not: test -f "$f"
[[ "$x" =~ ^[0-9]+$ ]]   # not: echo "$x" | grep -E ...

Profiling:

# Whole-script wall time:
time ./script.sh

# Per-line trace with timestamps:
PS4='+ $EPOCHREALTIME\011 '
set -x
# ... your script ...

$EPOCHREALTIME (Bash 5+) is microsecond-precision. The trace shows where time goes.

Built-in times — print user/sys CPU since shell start:

times
# 0m0.012s 0m0.008s
# 0m0.000s 0m0.000s

Read input efficiently:

# Slow — fork per line
while IFS= read -r line; do
    echo "$line"
done < big_file

# Faster — read whole file into memory
mapfile -t lines < big_file
for line in "${lines[@]}"; do
    echo "$line"
done

Async — background processes:

results=()
for host in "${hosts[@]}"; do
    {
        ping -c1 "$host" > /tmp/p_$$_${host} &
    }
done
wait                # block until all finish

The & runs in background. wait (no args) waits for ALL children.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…