Name Reference Functions for Performance Improvement
Problem
Non Negligible Cost of $() spawn. when we want to call function and capture its output to a variable.
Solution
Name reference pattern gives us ability to deal with Non Negligible Cost of $() spawn. in quite elegant way. By allowing to pass in a reference variable to a function, letting that function modify that variable. Think of it as passing an object reference in programming languages.
Syntax
local -n _out_some_name="${1:?name of external variable}"
Simple example
foo() {
local -n _out_some_name="${1:?name of external variable}"
_out_some_name=42
}
export -f foo
main() {
local main_var
# We pass a name reference of main_var to foo for foo to modify.
foo main_var
# Now we can get 42 without having to spawn a child shell $(foo)
echo "${main_var:?}"
}
main "${@}" || exit 1
Real Example:
Real example
# Sets a variable to the current time in milliseconds without spawning a subshell.
#
# This function uses bash nameref to directly set the caller's variable, avoiding
# the performance overhead of command substitution $().
#
# Usage:
# put__now_millis <var_name>
#
# Arguments:
# var_name - Name of the variable to set with the current time in milliseconds
#
# Example:
# ```
# # local usage is optional but recommended for scope, otherwise variables will be global
# local start_time end_time
# put__now_millis start_time
# do_something
# put__now_millis end_time
# echo "Operation took $((end_time - start_time))ms"
# ```
#
# Note: Requires Bash 4.3+ for nameref support
# Note: Requires Bash 5+ for EPOCHREALTIME
put__now_millis() {
# $1 is the NAME of the variable to set '__out' could be any variable name
local -n __out=$1
if [[ -n "${EPOCHREALTIME}" ]]; then
local epoch_micro="${EPOCHREALTIME//.}"
__out="$((epoch_micro / 1000))"
else
__out="$(gnu_date +%s%3N)" || throw "put__now_millis: Failed to [gnu_date +%s%3N]"
fi
}
export -f put__now_millis
Example usage:
foo() {
local start_millis end_millis
put__now_millis start_millis
sleep 0.1
put__now_millis end_millis
echo "Time for execution: $((end_millis - start_millis)) ms"
}
Example timing difference of name ref vs $() showing 100X improvement
Timing Code
foo() {
local start_millis
for i in {1..1000} ; do
put__now_millis start_millis
done
}
bar() {
local start_millis
for i in {1..1000} ; do
start_millis="$(now_millis)"
done
}
main() {
echo "---------------------------------------------------------------"
echo "Time 1000 put__now_millis calls"
time foo
echo "---------------------------------------------------------------"
echo "Time 1000 \$(now_millis) calls"
time bar
}
main "${@}" || exit 1
Timing Output
Time 1000 put__now_millis calls
real 0m0.013s
user 0m0.013s
sys 0m0.000s
---------------------------------------------------------------
Time 1000 $(now_millis) calls
real 0m1.813s
user 0m0.135s
sys 0m1.710s
Official Documentation
A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands (see the descriptions of declare and local below) to create a nameref, or a reference to another variable. This allows variables to be manipulated indirectly. Whenever the nameref variable is referenced, assigned to, unset, or has its attributes modified (other than using or changing the nameref attribute itself), the operation is actually performed on the variable specified by the nameref variable's value. A nameref is commonly used within shell functions to refer to a variable whose name is passed as an argument to the function. For instance, if a variable name is passed to a shell function as its first argument, running
declare -n ref=$1
inside the function creates a local nameref variable ref whose value is the variable name passed as the first argument. References and assignments to ref, and changes to its attributes, are treated as references, assignments, and attribute modifications to the variable whose name was passed as $1. If the control variable in a for loop has the nameref attribute, the list of words can be a list of shell variables, and a name reference is established for each word in the list, in turn, when the loop is executed. Array variables cannot be given the nameref attribute. However, nameref variables can reference array variables and subscripted array variables. Namerefs can be unset using the -n option to the unset builtin. Otherwise, if unset is executed with the name of a nameref variable as an argument, the variable referenced by the nameref variable is unset. - bash manpage
Requires modern bash
- Does require Bash 4.3+
Backlinks