Prerequisites
Steps to reproduce
switch -regex builds a new Regex for every input object that matches, instead of reusing a
cached one. The script below is self-contained and prints the numbers quoted under
Actual behavior.
# 1. Build a 200,000-line log.
$log = Join-Path ([IO.Path]::GetTempPath()) 'switch-regex-bench.log'
if (-not (Test-Path $log)) {
$sb = [System.Text.StringBuilder]::new()
for ($i = 1; $i -le 200000; $i++) {
$null = $sb.AppendLine("2026-09-04T00:00:0$($i % 10) ERROR thing $i failed")
}
Set-Content -Path $log -Value $sb.ToString() -NoNewline
}
function Measure-Median ([scriptblock] $Action, [int] $Count = 5) {
$times = foreach ($i in 1..$Count) { (Measure-Command $Action).TotalMilliseconds }
($times | Sort-Object)[[int]($Count / 2)]
}
# 2. Scenario A: one clause, every line matches.
$a = Measure-Median {
switch -regex -file $log {
'^(?<ts>\S+) ERROR (?<msg>.*)$' { $null = $matches }
}
}
# 3. Scenario B: 23 non-matching clauses ahead of the matching one, so 24 distinct
# patterns are evaluated per line -- more than [regex]::CacheSize, which is 15.
$clauses = (1..23 | ForEach-Object { "'NOMATCH{0:d2}(?<a>x)' {{ continue }}" -f $_ }) -join "`n "
$b = Measure-Median ([scriptblock]::Create(@"
switch -regex -file `$log {
$clauses
'^(?<ts>\S+) ERROR (?<msg>.*)$' { `$null = `$matches }
}
"@))
# 4. Allocation cost of scenario A.
[GC]::Collect(); [GC]::WaitForPendingFinalizers(); [GC]::Collect()
$before = [GC]::GetTotalAllocatedBytes($true)
switch -regex -file $log { '^(?<ts>\S+) ERROR (?<msg>.*)$' { $null = $matches } }
$mb = ([GC]::GetTotalAllocatedBytes($true) - $before) / 1MB
'Scenario A : {0:N1} ms (1 clause, median of 5)' -f $a
'Scenario B : {0:N1} ms (24 clauses, median of 5)' -f $b
'Ratio B/A : {0:N1}x' -f ($b / $a)
'Allocated : {0:N1} MB (scenario A)' -f $mb
Expected behavior
Each distinct clause pattern compiles once and is reused, the way -match, -replace and -split
already behave. Adding clauses costs one extra match attempt per line, not a recompile, so
scenario B stays a small multiple of scenario A.
Actual behavior
PowerShell 7.6.5, 200000 lines, median of 5 runs:
Scenario A (1 clause) : 1690.4 ms
Scenario B (24 clauses) : 22560.2 ms 13.3x, from clause count alone
Allocated, scenario A : 1008.6 MB about 5 KB per matched line
Every matching line constructs a fresh Regex. Once the clause count passes Regex.CacheSize (15),
the static cache thrashes and every clause recompiles for every line.
Error details
Environment data
Name Value
---- -----
PSVersion 7.6.5
PSEdition Core
GitCommitId 7.6.5
OS Arch Linux
Platform Unix
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.4
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
Visuals
No response
Prerequisites
Steps to reproduce
switch -regexbuilds a newRegexfor every input object that matches, instead of reusing acached one. The script below is self-contained and prints the numbers quoted under
Actual behavior.
Expected behavior
Actual behavior
Error details
Environment data
Visuals
No response