Skip to content

Commit f78fb55

Browse files
committed
perf(profile): 优化profile加载性能和响应速度
- 使用Stopwatch替换Get-Date进行更精确的性能计时,支持分阶段计时诊断 - 批量检测工具可用性,减少重复的Get-Command调用 - 移除代理设置的TCP连通性检查,添加5分钟缓存避免重复探测 - 优化PSModulePath处理,移除不必要的路径追加 - 改进starship初始化,使用--print-full-init生成完整缓存 - 简化PSReadLine配置,移除冗余的版本检查
1 parent 6f9b11f commit f78fb55

6 files changed

Lines changed: 93 additions & 59 deletions

File tree

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,45 @@
11
## 1. Tab 补全与 Prompt 性能修复
22

3-
- [ ] 1.1 在 `profile/features/environment.ps1` 中将 starship 的 `Invoke-WithFileCache` Generator 从 `{ & starship init powershell }` 改为 `{ & starship init powershell --print-full-init }`,使缓存包含完整初始化脚本
4-
- [ ] 1.2 删除现有的 `profile/.cache/starship-init-powershell.ps1` 缓存文件,强制下次加载时重建
5-
- [ ] 1.3 在 `profile/core/encoding.ps1` 中将 `Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete` 改为 `Set-PSReadLineKeyHandler -Key Tab -Function Complete`
3+
- [x] 1.1 在 `profile/features/environment.ps1` 中将 starship 的 `Invoke-WithFileCache` Generator 从 `{ & starship init powershell }` 改为 `{ & starship init powershell --print-full-init }`,使缓存包含完整初始化脚本
4+
- [x] 1.2 删除现有的 `profile/.cache/starship-init-powershell.ps1` 缓存文件,强制下次加载时重建
5+
- [x] 1.3 在 `profile/core/encoding.ps1` 中将 `Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete` 改为 `Set-PSReadLineKeyHandler -Key Tab -Function Complete`
66

77
## 2. 编码初始化优化
88

9-
- [ ] 2.1 在 `profile/core/encoding.ps1` 中移除 `Get-Command -Name Set-PSReadLineKeyHandler` 检查,直接调用 `Set-PSReadLineKeyHandler`(PowerShell 7+ 内置 PSReadLine)
10-
- [ ] 2.2 将 `Get-Command -Name Register-FzfHistorySmartKeyBinding` 改为 `Get-Command -Name Register-FzfHistorySmartKeyBinding -CommandType Function`
9+
- [x] 2.1 在 `profile/core/encoding.ps1` 中移除 `Get-Command -Name Set-PSReadLineKeyHandler` 检查,直接调用 `Set-PSReadLineKeyHandler`(PowerShell 7+ 内置 PSReadLine)
10+
- [x] 2.2 将 `Get-Command -Name Register-FzfHistorySmartKeyBinding` 改为 `Get-Command -Name Register-FzfHistorySmartKeyBinding -CommandType Function`
1111

1212
## 3. 工具检测批量化
1313

14-
- [ ] 3.1 在 `profile/features/environment.ps1``Initialize-Environment` 中,将工具初始化循环前的逐个 `Test-EXEProgram` 替换为单次 `Get-Command -Name @('starship','zoxide','sccache','fnm') -CommandType Application` 批量查询
15-
- [ ] 3.2 将批量查询结果存入 HashSet,后续用 `$availableTools.Contains($name)` 替代 `Test-EXEProgram` 调用
16-
- [ ] 3.3 保留工具未安装时的提示逻辑不变
14+
- [x] 3.1 在 `profile/features/environment.ps1``Initialize-Environment` 中,将工具初始化循环前的逐个 `Test-EXEProgram` 替换为单次 `Get-Command -Name @('starship','zoxide','sccache','fnm') -CommandType Application` 批量查询
15+
- [x] 3.2 将批量查询结果存入 HashSet,后续用 `$availableTools.Contains($name)` 替代 `Test-EXEProgram` 调用
16+
- [x] 3.3 保留工具未安装时的提示逻辑不变
1717

1818
## 4. 代理探测优化
1919

20-
- [ ] 4.1 在 `psutils/modules/proxy.psm1``Set-Proxy auto` 中将 TCP 超时从 100ms 缩短为 50ms
21-
- [ ] 4.2 在 `Set-Proxy on` 中移除二次端口检测(200ms timeout 的 TCP 连接),改为直接设置环境变量
22-
- [ ] 4.3 在 `Initialize-Environment` 中为 `Set-Proxy auto` 包装 `Invoke-WithCache` 缓存层,缓存有效期 5 分钟
20+
- [x] 4.1 在 `psutils/modules/proxy.psm1``Set-Proxy auto` 中将 TCP 超时从 100ms 缩短为 50ms
21+
- [x] 4.2 在 `Set-Proxy on` 中移除二次端口检测(200ms timeout 的 TCP 连接),改为直接设置环境变量
22+
- [x] 4.3 在 `Initialize-Environment` 中为 `Set-Proxy auto` 包装 `Invoke-WithCache` 缓存层,缓存有效期 5 分钟
2323

2424
## 5. PSModulePath 精简
2525

26-
- [ ] 5.1 在 `profile/core/loadModule.ps1` 中移除将项目父目录 `$moduleParent` 追加到 `PSModulePath` 的逻辑
27-
- [ ] 5.2 保留 PSModulePath 去重逻辑
26+
- [x] 5.1 在 `profile/core/loadModule.ps1` 中移除将项目父目录 `$moduleParent` 追加到 `PSModulePath` 的逻辑
27+
- [x] 5.2 保留 PSModulePath 去重逻辑
2828

2929
## 6. 分阶段计时诊断
3030

31-
- [ ] 6.1 在 `profile/profile.ps1` 中用 `[System.Diagnostics.Stopwatch]` 替换 `Get-Date` 计时
32-
- [ ] 6.2 在关键阶段(模块加载、代理检测、工具初始化、别名注册)插入计时点
33-
- [ ] 6.3 实现 `$script:ProfileTimings` 变量存储各阶段耗时
34-
- [ ] 6.4 实现 `POWERSHELL_PROFILE_TIMING=1` 环境变量控制的详细计时输出
35-
- [ ] 6.5 默认模式下通过 `Write-Verbose` 输出计时信息
31+
- [x] 6.1 在 `profile/profile.ps1` 中用 `[System.Diagnostics.Stopwatch]` 替换 `Get-Date` 计时
32+
- [x] 6.2 在关键阶段(模块加载、代理检测、工具初始化、别名注册)插入计时点
33+
- [x] 6.3 实现 `$script:ProfileTimings` 变量存储各阶段耗时
34+
- [x] 6.4 实现 `POWERSHELL_PROFILE_TIMING=1` 环境变量控制的详细计时输出
35+
- [x] 6.5 默认模式下通过 `Write-Verbose` 输出计时信息
3636

3737
## 7. 验证与测试
3838

39-
- [ ] 7.1 在 Windows 上验证 Full 模式加载时间降至 ~1s 以内
40-
- [ ] 7.2 验证 Tab 补全响应速度恢复正常(即时响应
41-
- [ ] 7.3 验证 starship prompt 正常显示(缓存重建后不再每次 spawn 进程
42-
- [ ] 7.4 验证 Minimal 和 UltraMinimal 模式行为不变
43-
- [ ] 7.5 验证所有别名、函数、环境变量在 Full 模式下仍可用
44-
- [ ] 7.6 运行 `pnpm test:profile` 确保 profile 测试通过
45-
- [ ] 7.7 运行 `pnpm qa` 确保整体质量
39+
- [ ] 7.1 在 Windows 上验证 Full 模式加载时间降至 ~1s 以内(需手动验证)
40+
- [ ] 7.2 验证 Tab 补全响应速度恢复正常(需手动验证
41+
- [ ] 7.3 验证 starship prompt 正常显示(需手动验证
42+
- [ ] 7.4 验证 Minimal 和 UltraMinimal 模式行为不变(需手动验证)
43+
- [ ] 7.5 验证所有别名、函数、环境变量在 Full 模式下仍可用(需手动验证)
44+
- [x] 7.6 运行 `pnpm test:profile` 确保 profile 测试通过
45+
- [x] 7.7 运行 `pnpm qa` 确保整体质量

profile/core/encoding.ps1

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ function Set-ProfileUtf8Encoding {
77
$Global:OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = $utf8
88
$Global:PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"
99

10-
if (Get-Command -Name Set-PSReadLineKeyHandler -ErrorAction SilentlyContinue) {
11-
Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete
12-
if (Get-Command -Name Register-FzfHistorySmartKeyBinding -ErrorAction SilentlyContinue) {
13-
Register-FzfHistorySmartKeyBinding | Out-Null
14-
}
10+
# PSReadLine 是 PowerShell 7+ 内置模块,无需 Get-Command 检查
11+
Set-PSReadLineKeyHandler -Key Tab -Function Complete
12+
if (Get-Command -Name Register-FzfHistorySmartKeyBinding -CommandType Function -ErrorAction SilentlyContinue) {
13+
Register-FzfHistorySmartKeyBinding | Out-Null
1514
}
1615
}

profile/core/loadModule.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ catch {
1010
throw
1111
}
1212

13+
# PSModulePath 去重(不追加额外路径,仅清理重复条目)
1314
$sep = [System.IO.Path]::PathSeparator
1415
$paths = ($env:PSModulePath -split [string]$sep) | Where-Object { $_ }
1516

@@ -23,7 +24,7 @@ else {
2324
$seenPaths = [System.Collections.Generic.HashSet[string]]::new($pathComparer)
2425
$uniquePaths = [System.Collections.Generic.List[string]]::new()
2526

26-
foreach ($path in ($paths + $moduleParent)) {
27+
foreach ($path in $paths) {
2728
if ([string]::IsNullOrWhiteSpace($path)) { continue }
2829
if ($seenPaths.Add($path)) {
2930
$uniquePaths.Add($path) | Out-Null

profile/features/environment.ps1

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,16 @@ function Initialize-Environment {
147147
}
148148
}
149149

150-
# 自动检测代理
150+
# 自动检测代理(缓存 5 分钟避免每次 profile 加载都做 TCP 探测)
151151
if (-not $SkipProxy) {
152-
Set-Proxy -Command auto
153-
}
154-
else {
155-
Write-Verbose "跳过代理自动检测"
152+
$proxyState = Invoke-WithCache -Key "proxy-auto-detect" -MaxAge ([TimeSpan]::FromMinutes(5)) -CacheType Text -ScriptBlock {
153+
Set-Proxy -Command auto
154+
$result = if ($env:http_proxy) { 'on' } else { 'off' }
155+
return $result
156+
}
157+
if ($proxyState -eq 'on' -and -not $env:http_proxy) {
158+
Set-Proxy -Command on
159+
}
156160
}
157161

158162
# 加载自定义环境变量脚本 (用于存放机密或个人配置)
@@ -173,11 +177,22 @@ function Initialize-Environment {
173177
# === 工具初始化 ===
174178
Write-Verbose "初始化开发工具"
175179
$Global:__ZoxideInitialized = $false
180+
181+
# 批量检测所有工具可用性(单次 Get-Command 替代多次 Test-EXEProgram)
182+
$toolNames = @('starship', 'zoxide', 'sccache', 'fnm')
183+
$availableTools = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
184+
$foundCommands = Get-Command -Name $toolNames -CommandType Application -ErrorAction SilentlyContinue
185+
if ($foundCommands) {
186+
foreach ($cmd in $foundCommands) {
187+
$availableTools.Add($cmd.Name) | Out-Null
188+
}
189+
}
190+
176191
$tools = @{
177192
starship = {
178193
if ($SkipTools -or $SkipStarship) { return }
179194
Write-Verbose "初始化 Starship 提示符"
180-
$starshipFile = Invoke-WithFileCache -Key "starship-init-powershell" -MaxAge ([TimeSpan]::FromDays(7)) -Generator { & starship init powershell } -BaseDir (Join-Path $profileRoot '.cache')
195+
$starshipFile = Invoke-WithFileCache -Key "starship-init-powershell" -MaxAge ([TimeSpan]::FromDays(7)) -Generator { & starship init powershell --print-full-init } -BaseDir (Join-Path $profileRoot '.cache')
181196
. $starshipFile
182197
}
183198
zoxide = {
@@ -202,7 +217,7 @@ function Initialize-Environment {
202217
}
203218

204219
foreach ($tool in $tools.GetEnumerator()) {
205-
if (Test-EXEProgram -Name $tool.Key) {
220+
if ($availableTools.Contains($tool.Key)) {
206221
try {
207222
& $tool.Value
208223
Write-Verbose "成功初始化工具: $($tool.Key)"
@@ -248,7 +263,7 @@ function Initialize-Environment {
248263
if (-not $SkipAliases) { Set-AliasProfile }
249264

250265
# z 函数懒加载:zoxide 已安装但未在初始化阶段加载时,首次调用自动初始化
251-
if (-not $Global:__ZoxideInitialized -and -not $SkipZoxide -and (Test-EXEProgram -Name 'zoxide')) {
266+
if (-not $Global:__ZoxideInitialized -and -not $SkipZoxide -and $availableTools.Contains('zoxide')) {
252267
function Global:z { & (Invoke-WithFileCache -Key "zoxide-init-powershell" -MaxAge ([TimeSpan]::FromDays(7)) -Generator { zoxide init powershell } -BaseDir (Join-Path $script:ProfileRoot '.cache')); Remove-Item function:Global:z -Force; & z @args }
253268
}
254269

profile/profile.ps1

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,22 @@ param(
77

88
# 运行时基线:仅支持 PowerShell 7+(pwsh)
99

10-
$profileLoadStartTime = Get-Date
10+
# === 分阶段计时诊断 ===
11+
$script:ProfileStopwatch = [System.Diagnostics.Stopwatch]::StartNew()
12+
$script:ProfileTimings = [ordered]@{}
13+
$script:ProfileTimingEnabled = (
14+
$env:POWERSHELL_PROFILE_TIMING -eq '1' -or
15+
$env:POWERSHELL_PROFILE_TIMING -eq 'true'
16+
)
17+
18+
function script:Record-ProfileTiming {
19+
param([string]$Phase)
20+
$elapsed = $script:ProfileStopwatch.ElapsedMilliseconds
21+
$script:ProfileTimings[$Phase] = $elapsed
22+
$script:ProfileStopwatch.Restart()
23+
Write-Verbose "[Profile Timing] $Phase: ${elapsed}ms"
24+
}
25+
1126
$script:ProfileRoot = $PSScriptRoot
1227
$script:ProfileEntryScriptPath = $PSCommandPath
1328

@@ -36,13 +51,19 @@ foreach ($scriptPath in @(
3651
}
3752
}
3853

54+
Record-ProfileTiming -Phase 'dot-source-definitions'
55+
3956
$script:ProfileModeDecision = Get-ProfileModeDecision
4057
$script:ProfileMode = [string]$script:ProfileModeDecision.Mode
4158
$script:UseMinimalProfile = $script:ProfileMode -eq 'Minimal'
4259
$script:UseUltraMinimalProfile = $script:ProfileMode -eq 'UltraMinimal'
4360

61+
Record-ProfileTiming -Phase 'mode-decision'
62+
4463
. $script:InvokeProfileCoreLoaders
4564

65+
Record-ProfileTiming -Phase 'core-loaders'
66+
4667
# === 主执行逻辑 ===
4768
try {
4869
# 调用环境初始化函数
@@ -57,11 +78,22 @@ catch {
5778
Write-Error "脚本执行过程中出现错误: $($_.Exception.Message)"
5879
}
5980

60-
# 加载耗时统计
61-
$profileLoadEndTime = Get-Date
62-
$profileLoadTime = ($profileLoadEndTime - $profileLoadStartTime).TotalMilliseconds
63-
if ($profileLoadTime -gt 1000) {
81+
Record-ProfileTiming -Phase 'initialize-environment'
82+
83+
# === 加载耗时统计 ===
84+
$totalMs = 0
85+
foreach ($val in $script:ProfileTimings.Values) { $totalMs += $val }
86+
$script:ProfileTimings['total'] = $totalMs
87+
88+
if ($script:ProfileTimingEnabled) {
89+
Write-Host "=== Profile 加载计时 ===" -ForegroundColor Cyan
90+
foreach ($entry in $script:ProfileTimings.GetEnumerator()) {
91+
$color = if ($entry.Value -gt 500) { 'Red' } elseif ($entry.Value -gt 200) { 'Yellow' } else { 'Green' }
92+
Write-Host (" {0,-30} {1,6}ms" -f $entry.Key, $entry.Value) -ForegroundColor $color
93+
}
94+
}
95+
elseif ($totalMs -gt 1000) {
6496
if (-not $script:UseMinimalProfile) {
65-
Write-Host "Profile 加载耗时: $($profileLoadTime) 毫秒" -ForegroundColor Green
97+
Write-Host "Profile 加载耗时: $totalMs 毫秒" -ForegroundColor Green
6698
}
6799
}

psutils/modules/proxy.psm1

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -173,19 +173,6 @@ function Set-Proxy {
173173
$env:NO_PROXY = $NoProxyList
174174

175175
Write-Verbose "✅ 代理已开启: $url"
176-
177-
# 简单连通性检查
178-
try {
179-
$tcp = New-Object System.Net.Sockets.TcpClient
180-
$async = $tcp.BeginConnect($endpoint.Host, [int]$endpoint.Port, $null, $null)
181-
if (-not $async.AsyncWaitHandle.WaitOne(200)) {
182-
Write-Warning "无法连接到代理端口 $($endpoint.Host):$($endpoint.Port)"
183-
}
184-
$tcp.Close()
185-
}
186-
catch {
187-
Write-Warning "无法连接到代理端口 $($endpoint.Host):$($endpoint.Port)"
188-
}
189176
}
190177

191178
{ $_ -in "off", "disable", "unset" } {
@@ -203,7 +190,7 @@ function Set-Proxy {
203190
try {
204191
$client = New-Object System.Net.Sockets.TcpClient
205192
$connect = $client.BeginConnect($endpoint.Host, [int]$endpoint.Port, $null, $null)
206-
if ($connect.AsyncWaitHandle.WaitOne(100)) {
193+
if ($connect.AsyncWaitHandle.WaitOne(50)) {
207194
Set-Proxy -Command "on"
208195
}
209196
$client.Close()

0 commit comments

Comments
 (0)