Subject: [PATCH] Speed up bash completion loading

From: Jonathan Nieder <[email-masked]>
Date: Fri, 13 Nov 2009 02:50:28 -0600

On my slow laptop (P3 600MHz), system-wide bash completions take
too much time to load (> 2s), and a significant fraction of this
time is spent loading git-completion.bash:

$ time bash -c ". ./git-completion.bash"  # hot cache, before this patch

real    0m0.509s
user    0m0.310s
sys     0m0.180s

Kirill tracked down that the most time is spent warming up
merge_strategy, all_command & porcelain_command caches.

Since git is not used in each and every interactive xterm, it
seems best to load completion support with cold caches, and then
load each needed thing lazily.  This has most of the speed
advantage of pre-generating everything at build time, without the
complication of figuring out at build time what commands will be
available at run time.

The result is that loading completion is significantly faster
now:

$ time bash -c ". ./git-completion.bash"  # cold cache, after this patch

real    0m0.171s
user    0m0.060s
sys     0m0.040s

Suggested-by: Kirill Smelkov <[email-masked]>
Cc: Shawn O. Pearce <[email-masked]>
Cc: Stephen Boyd <[email-masked]>
Cc: Sverre Rabbelier <[email-masked]>
Cc: Junio C Hamano <[email-masked]>
Signed-off-by: Jonathan Nieder <[email-masked]>
---
Stephen Boyd wrote:

> Ah ok. I think this proves even more that pregenerating the
> completion is a bad idea. With dynamic population we don't have
> these problems and it only takes 250ms more to load on a P3 700Mhz.

Hmm, 250 ms is a lot.

Strictly speaking, even with the existing completion script, it might
be nice to provide a "hash -r"-like command to bring the caches up to
date.  Such a function could be:

git-completion-rehash ()
{
	__git_compute_all_commands
	__git_compute_merge_strategies
	__git_compute_porcelain_commands
}

But that is a separate topic.

 contrib/completion/git-completion.bash |   35 +++++++++++++++++--------------
 1 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 9e8b607..7088ec7 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -330,10 +330,9 @@ __git_list_merge_strategies ()
 	}'
 }
 
-__git_merge_strategies ()
+__git_compute_merge_strategies ()
 {
-	: ${__git_merge_strategylist:=$(__git_list_merge_strategies)}
-	echo "$__git_merge_strategylist"
+	: ${__git_merge_strategies=$(__git_list_merge_strategies)}
 }
 
 __git_complete_file ()
@@ -468,15 +467,16 @@ __git_complete_remote_or_refspec ()
 
 __git_complete_strategy ()
 {
+	__git_compute_merge_strategies
 	case "${COMP_WORDS[COMP_CWORD-1]}" in
 	-s|--strategy)
-		__gitcomp "$(__git_merge_strategies)"
+		__gitcomp "$__git_merge_strategies"
 		return 0
 	esac
 	local cur="${COMP_WORDS[COMP_CWORD]}"
 	case "$cur" in
 	--strategy=*)
-		__gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
+		__gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}"
 		return 0
 		;;
 	esac
@@ -495,16 +495,16 @@ __git_list_all_commands ()
 	done
 }
 
-__git_all_commands ()
+__git_compute_all_commands ()
 {
-	: ${__git_all_commandlist:=$(__git_list_all_commands)}
-	echo "$__git_all_commandlist"
+	: ${__git_all_commands=$(__git_list_all_commands)}
 }
 
 __git_list_porcelain_commands ()
 {
 	local i IFS=" "$'\n'
-	for i in "help" $(__git_all_commands)
+	__git_compute_all_commands
+	for i in "help" $__git_all_commands
 	do
 		case $i in
 		*--*)             : helper pattern;;
@@ -586,10 +586,9 @@ __git_list_porcelain_commands ()
 	done
 }
 
-__git_porcelain_commands ()
+__git_compute_porcelain_commands ()
 {
-	: ${__git_porcelain_commandlist:=$(__git_list_porcelain_commands)}
-	echo "$__git_porcelain_commandlist"
+	: ${__git_porcelain_commands=$(__git_list_porcelain_commands)}
 }
 
 __git_aliases ()
@@ -1082,7 +1081,8 @@ _git_help ()
 		return
 		;;
 	esac
-	__gitcomp "$(__git_all_commands)
+	__git_compute_all_commands
+	__gitcomp "__git_all_commands
 		attributes cli core-tutorial cvs-migration
 		diffcore gitk glossary hooks ignore modules
 		repository-layout tutorial tutorial-2
@@ -1438,7 +1438,8 @@ _git_config ()
 		return
 		;;
 	pull.twohead|pull.octopus)
-		__gitcomp "$(__git_merge_strategies)"
+		__git_compute_merge_strategies
+		__gitcomp "$__git_merge_strategies"
 		return
 		;;
 	color.branch|color.diff|color.interactive|\
@@ -1539,7 +1540,8 @@ _git_config ()
 	pager.*)
 		local pfx="${cur%.*}."
 		cur="${cur#*.}"
-		__gitcomp "$(__git_all_commands)" "$pfx" "$cur"
+		__git_compute_all_commands
+		__gitcomp "__git_all_commands" "$pfx" "$cur"
 		return
 		;;
 	remote.*.*)
@@ -2136,7 +2138,8 @@ _git ()
 			--help
 			"
 			;;
-		*)     __gitcomp "$(__git_porcelain_commands) $(__git_aliases)" ;;
+		*)     __git_compute_porcelain_commands
+		       __gitcomp "$__git_porcelain_commands $(__git_aliases)" ;;
 		esac
 		return
 	fi
-- 
1.6.5.2