From 256ccc34a7fa2fca634b2acd0c6f429b2afe1c9d Mon Sep 17 00:00:00 2001 From: Dom Slee Date: Sat, 16 Sep 2023 22:57:13 +1000 Subject: [PATCH] Improve init performance by not calling posh-tabcomplete during init (#14) --- README.md | 8 +++--- benchmark/all.md | 18 ++++++------ benchmark/complete/benchmark_complete.ps1 | 5 +--- benchmark/init/benchmark_init.ps1 | 2 +- benchmark/profiles/ProfileTabComplete.ps1 | 2 +- benchmark/util.ps1 | 2 +- resource/init.ps1 | 6 ++-- scripts/watch.ps1 | 1 + src/main.rs | 35 ++++++++++++++--------- 9 files changed, 43 insertions(+), 36 deletions(-) create mode 100644 scripts/watch.ps1 diff --git a/README.md b/README.md index a3a742b..7956132 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,10 @@ The completions packaged with the binary in [completions.nu](./resource/completi ## Benchmarks To run these, run `./benchmark/benchmark_all.ps1` -| Benchmark | Results | -| ---------------------------------------------------- | ------------------------------------------------------- | -| `benchmark/init` - startup time | posh-tabcomplete: 102ms, posh-git: 432ms (4.24x faster) | -| `benchmark/complete` - tab completion (100 branches) | posh-tabcomplete: 71ms, posh-git: 172ms (2.42x faster) | +| Benchmark | Results | +| ---------------------------------------------------- | ------------------------------------------------------ | +| `benchmark/init` - startup time | posh-tabcomplete: 80ms, posh-git: 307ms (3.84x faster) | +| `benchmark/complete` - tab completion (100 branches) | posh-tabcomplete: 80ms, posh-git: 178ms (2.22x faster) | ## Aliases / Function support Functions are supported. For example, the completion of `gco` in the demo is: diff --git a/benchmark/all.md b/benchmark/all.md index b76c940..2a789f9 100644 --- a/benchmark/all.md +++ b/benchmark/all.md @@ -1,21 +1,21 @@ # All results -file size: 14286336 +file size: 14286848 ## Init | Command | Mean [ms] | Min [ms] | Max [ms] | Relative | |:---|---:|---:|---:|---:| -| `pwsh -NoProfile -File ./../profiles/ProfileBaseline.ps1` | 234.5 ± 6.0 | 226.0 | 250.7 | 1.00 | -| `pwsh -NoProfile -File ./../profiles/ProfilePoshGit.ps1` | 525.4 ± 6.2 | 514.2 | 541.2 | 2.24 ± 0.06 | -| `pwsh -NoProfile -File ./../profiles/ProfileTabComplete.ps1` | 295.1 ± 4.9 | 288.2 | 311.9 | 1.26 ± 0.04 | +| `pwsh -NoProfile -File ./../profiles/ProfileBaseline.ps1` | 227.1 ± 4.8 | 220.2 | 240.1 | 1.00 | +| `pwsh -NoProfile -File ./../profiles/ProfilePoshGit.ps1` | 534.8 ± 30.9 | 505.8 | 624.0 | 2.35 ± 0.14 | +| `pwsh -NoProfile -File ./../profiles/ProfileTabComplete.ps1` | 307.8 ± 22.4 | 268.0 | 370.3 | 1.36 ± 0.10 | -tabcomplete: 60ms, posh-git: 290ms (4.83x faster) +posh-tabcomplete: 80ms, posh-git: 307ms (3.84x faster) ## Complete | Command | Mean [ms] | Min [ms] | Max [ms] | Relative | |:---|---:|---:|---:|---:| -| `pwsh -NoProfile -File CompleteBaseline.ps1` | 574.8 ± 9.2 | 561.8 | 599.1 | 1.00 | -| `pwsh -NoProfile -File CompletePoshGit.ps1` | 761.5 ± 8.6 | 746.9 | 780.7 | 1.32 ± 0.03 | -| `pwsh -NoProfile -File CompleteTabComplete.ps1` | 660.5 ± 10.5 | 647.3 | 694.2 | 1.15 ± 0.03 | +| `pwsh -NoProfile -File CompleteBaseline.ps1` | 546.7 ± 7.8 | 536.8 | 574.4 | 1.00 | +| `pwsh -NoProfile -File CompletePoshGit.ps1` | 724.9 ± 7.3 | 713.6 | 746.7 | 1.33 ± 0.02 | +| `pwsh -NoProfile -File CompleteTabComplete.ps1` | 627.3 ± 5.4 | 617.5 | 641.5 | 1.15 ± 0.02 | -tabcomplete: 85ms, posh-git: 186ms (2.19x faster) +posh-tabcomplete: 80ms, posh-git: 178ms (2.22x faster) diff --git a/benchmark/complete/benchmark_complete.ps1 b/benchmark/complete/benchmark_complete.ps1 index e4a5726..aee7bd9 100644 --- a/benchmark/complete/benchmark_complete.ps1 +++ b/benchmark/complete/benchmark_complete.ps1 @@ -5,9 +5,6 @@ if (Test-Path "$childRepoDir") { Remove-Item -r -fo "$childRepoDir" } -# ensure posh-git is installed -$(Get-InstalledModule -Name "posh-git").Version - Write-Output "set up childRepoDir" mkdir "$childRepoDir" Set-Location "$childRepoDir" @@ -22,7 +19,7 @@ git commit -m "init" Set-Location "$PSScriptRoot" hyperfine ` --warmup 3 ` - --runs 25 ` + --runs 75 ` -L script CompleteBaseline.ps1,CompletePoshGit.ps1,CompleteTabComplete.ps1 ` "pwsh -NoProfile -File {script}" ` --export-markdown $PSScriptRoot/complete.md ` diff --git a/benchmark/init/benchmark_init.ps1 b/benchmark/init/benchmark_init.ps1 index 4f67498..f5cf217 100644 --- a/benchmark/init/benchmark_init.ps1 +++ b/benchmark/init/benchmark_init.ps1 @@ -3,7 +3,7 @@ Set-Location $PSScriptRoot hyperfine ` --warmup 3 ` - --runs 25 ` + --runs 75 ` -L profile ./../profiles/ProfileBaseline.ps1,./../profiles/ProfilePoshGit.ps1,./../profiles/ProfileTabComplete.ps1 ` "pwsh -NoProfile -File {profile}" ` --export-markdown init.md ` diff --git a/benchmark/profiles/ProfileTabComplete.ps1 b/benchmark/profiles/ProfileTabComplete.ps1 index 36692b6..38babb9 100644 --- a/benchmark/profiles/ProfileTabComplete.ps1 +++ b/benchmark/profiles/ProfileTabComplete.ps1 @@ -1 +1 @@ -Invoke-Expression (&tabcomplete init | Out-String) +Invoke-Expression (&posh-tabcomplete init | Out-String) diff --git a/benchmark/util.ps1 b/benchmark/util.ps1 index 6643285..4251b01 100644 --- a/benchmark/util.ps1 +++ b/benchmark/util.ps1 @@ -8,5 +8,5 @@ function GetMs { function GetSummary { param ([double] $poshMs, [double] $tabMs) - return "tabcomplete: ${tabMs}ms, posh-git: ${poshMs}ms ($([math]::Round($poshMs / $tabMs, 2))x faster)" + return "posh-tabcomplete: ${tabMs}ms, posh-git: ${poshMs}ms ($([math]::Round($poshMs / $tabMs, 2))x faster)" } \ No newline at end of file diff --git a/resource/init.ps1 b/resource/init.ps1 index 5c5b975..2a84c37 100644 --- a/resource/init.ps1 +++ b/resource/init.ps1 @@ -5,8 +5,10 @@ $null = New-Module posh-tabcomplete { "($($executables -join '|'))" } $EnableProxyFunctionExpansion = $true - $knownExecutables = @(&posh-tabcomplete nu-commands)#"git", "burrito" - $GitProxyFunctionRegex = "(^|[;`n])(\s*)(?$(Get-JoinPattern $knownExecutables))(?(([^\S\r\n]|[^\S\r\n]``\r?\n)+\S+)*)(([^\S\r\n]|[^\S\r\n]``\r?\n)+\`$args)(\s|``\r?\n)*($|[|;`n])" + $knownExecutables = ::TABCOMPLETE_NU_COMMANDS:: # @("git", "npm", "cargo") # @(&posh-tabcomplete nu-commands) + $GitProxyFunctionRegex = '(^|[;`n])(\s*)(?' + (Get-JoinPattern $knownExecutables) + ')(?(([^\S\r\n]|[^\S\r\n]`\r?\n)+\S+)*)(([^\S\r\n]|[^\S\r\n]`\r?\n)+\$args)(\s|`\r?\n)*($|[|;`n])' + # $GitProxyFunctionRegex = "(^|[;`n])(\s*)(?$(Get-JoinPattern $knownExecutables))(?(([^\S\r\n]|[^\S\r\n]``\r?\n)+\S+)*)(([^\S\r\n]|[^\S\r\n]``\r?\n)+\`$args)(\s|``\r?\n)*($|[|;`n])" + function Main { $cmdNames = Get-CommandNamesUsingRegex diff --git a/scripts/watch.ps1 b/scripts/watch.ps1 new file mode 100644 index 0000000..59e2a29 --- /dev/null +++ b/scripts/watch.ps1 @@ -0,0 +1 @@ +cargo watch -i ./src/*,./resource/* -s 'cargo install --path . --debug' \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 490acc9..499106d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,11 +26,8 @@ fn main() -> Result<(), std::io::Error> { pub fn run_with_args(root_args: &RootArgs) -> Result<(), std::io::Error> { match &root_args.subcommand { TabCompleteSubCommand::Complete(complete_args) => complete(root_args, complete_args), - TabCompleteSubCommand::NuCommands => get_nu_commands(root_args), - TabCompleteSubCommand::Init => { - init(); - Ok(()) - } + TabCompleteSubCommand::NuCommands => print_nu_commands(root_args), + TabCompleteSubCommand::Init => init(root_args), }?; Ok(()) } @@ -70,22 +67,32 @@ fn get_string_from_files(root_args: &RootArgs) -> String { } static INIT_DATA: &[u8] = include_bytes!("../resource/init.ps1"); -pub fn init() { - println!("{}", str::from_utf8(INIT_DATA).unwrap()); +pub fn init(root_args: &RootArgs) -> Result<(), io::Error> { + let string_data = str::from_utf8(INIT_DATA).unwrap(); + let commands = get_nu_commands(root_args)?; + let joined_commands = format!("@('{}')", commands.iter().join("', '")); + let replaced_string = string_data.replace("::TABCOMPLETE_NU_COMMANDS::", &joined_commands); + println!("{}", replaced_string); + Ok(()) +} + +pub fn print_nu_commands(root_args: &RootArgs) -> Result<(), io::Error> { + let s = get_nu_commands(root_args)?; + println!("{}", s.iter().join("\n")); + Ok(()) } -pub fn get_nu_commands(root_args: &RootArgs) -> Result<(), io::Error> { - let _string_from_files = get_string_from_files(root_args); - let nu_file_data = if _string_from_files.is_empty() { +pub fn get_nu_commands(root_args: &RootArgs) -> Result, io::Error> { + let string_from_files = get_string_from_files(root_args); + let nu_file_data = if string_from_files.is_empty() { String::from_utf8_lossy(DEFAULT_CONFIG_DATA).to_string() } else { - _string_from_files + string_from_files }; let re = Regex::new(r#"(?:^|\n)\s*export extern "\S+"#).unwrap(); let matches = re .find_iter(&nu_file_data) .map(|x| x.as_str().split('"').nth(1).unwrap()); - let s: HashSet<&str> = HashSet::from_iter(matches); - println!("{}", s.iter().join("\n")); - Ok(()) + let s: HashSet = HashSet::from_iter(matches.map(|m| m.to_string())); + Ok(s) }