Prev: The simplest case, Up: Index, Next: Expansions

External commands

In the previous article we have measured how long it takes to execute one of the simplest commands in UNIX-like systems: /usr/bin/true. Throughout this part we will refer to this kind of commands as external: they are stored in a standalone binary file somewhere in the filesystem, and can be used in any shell.

time for ((i=0; i<10000; i++)); do
    /usr/bin/true
done

real    0m5,829s
user    0m4,226s
sys     0m1,942s

However, we’ve also observed that running such commands involves a number of things the operating system needs to do to run it. Do we really need to load the same shared libraries everytime we call this or that command? Do we need to reach out to the filesystem to load them? Finally, do we always need to start a new process?

Historical shells

The first UNIX shell, written by Ken Thompson was very minimalistic — it supported only command execution, input/output redirection and pipes, thus every command execution involved process creation.

Its successor, Bourne shell, introduced some scripting capabilities in the form of conditional and control flow instructions. It also introduced some simple commands built into the shell, but only those needed to control the script itself (like exit, wait, return). Still commands like true, false or test remained external.

Built-in commands

Since true, false and test are heavily used in conditionals and loops, in order to reduce the overhead of starting a new process on each call, Bash provides their built-in equivalents. Let’s see how they perform in comparison to the external version:

time for ((i=0; i<10000; i++)); do
    true                            # built-in command
done

real    0m0,018s
user    0m0,014s
sys     0m0,004s

Using a built-in command gave us 33x improvement in the execution time and that matches better our intuition about how long it should take.

A similar result we get with the test command:

  • An external command

    time for ((i=0;i<10000;i++)); do
        /usr/bin/test -z ""
    done
    
    real    0m6,494s
    user    0m4,718s
    sys     0m2,148s
  • Built-in command

    time for ((i=0;i<10000;i++)); do
        test -z ""
    done
    
    real    0m0,099s
    user    0m0,039s
    sys     0m0,060s

Prev: The simplest case, Up: Index, Next: Expansions