Shell Benchmarks, Back to Writing › Benchmarking Merry [merry-benchmarks]
- July 4, 2026
- Patrick Ferris
Medium-to-small potatoes
- July 4, 2026
- Patrick Ferris
I used shellbench to understand where some of the performance pitfalls were coming from.
$ ./shellbench -s bash,zsh,msh sample/func.sh
-----------------------------------------------------------------------------------
name bash zsh msh
-----------------------------------------------------------------------------------
func.sh: no func 775,539 1,124,485 5,229
func.sh: func 398,861 197,169 93,424
-----------------------------------------------------------------------------------
* count: number of executions per second
This particular benchmark first runs nothing (i.e. :) and then a function lookup and execution. Surprisingly msh is slower at doing nothing! I perf-ed a run of msh test.sh with just a single : in the file:
Test built-in
- July 4, 2026
- Patrick Ferris
After making various improvements to Merry that almost halved the memory usage (e.g., removing extraneous string manipulations, adding a fast-path to the PATH variable for resolving programs). I was still pretty far off the performance of these other shells. I decided to have a look at dash which has a simple implementation.
This flame graph is for the following shell script:
f () { :; }
while __count=$(($__count+1)) && ["$__count" -le 10000 ]; do
f
done
echo "count is $__count"
And here are some rudimentary benchmarks:
$ time -p dash test.sh
count is 10001
real 0.01
user 0.01
sys 0.00
$ time -p msh test.sh
count is 10001
real 15.53
user 4.28
sys 12.28
It turns out the massive slowdown is very simple to explain. In the dash flamegraph there is a oexpr stack; which comes from the bltin/test.c file. dash, along with the other shells, does not spawn a process for the [-test command! Forcing dash to do use the binary [ gives:
$ time -p dash test.sh
count is 10001
real 7.55
user 3.95
sys 3.44
Which is much more respectable in terms of performance compared to msh. Plenty of work to do though!
Parsing arithmetic expressions
- July 4, 2026
- Patrick Ferris
There was some obvious low-hanging fruit to help improve some of the performance differences. The first was pre-parsing arithmetic expressions. Morbig, the static parser, does not implement arithmetic expression parsing; so Merry does it for us. Before, it was on-demand, as we encountered an expression of the form $((<e>)). Now, it happens up front. This should improve tight-loops.