Выполните итерацию резервного текстового файла ascii, найдите все экземпляры {LINE2 1-9999} и замените на {LINE2 "номер строки, в которой находится код". Перезапись. Быстрее?
Этот код работает. Я просто хочу посмотреть, насколько быстрее кто-то сможет заставить его работать.
Сделайте резервную копию вашего пакетного файла Windows 10 на случай, если что-то пойдет не так. Найдите все экземпляры строки {LINE2 1-9999} и замените на {LINE2 "номер строки, в которой находится код"}. Перезаписать, кодировать как ASCII.
Если _61.bat это:
TITLE %TIME% NO "%zmyapps1%\*.*" ARCHIVE ATTRIBUTE LINE2 1243
TITLE %TIME% DOC/SET YQJ8 LINE2 1887
SET ztitle=%TIME%: WINFOLD LINE2 2557
TITLE %TIME% _*.* IN WINFOLD LINE2 2597
TITLE %TIME% %%ZDATE1%% YQJ25 LINE2 3672
TITLE %TIME% FINISHED. PRESS ANY KEY TO SHUTDOWN ... LINE2 4922
Результаты:
TITLE %TIME% NO "%zmyapps1%\*.*" ARCHIVE ATTRIBUTE LINE2 1
TITLE %TIME% DOC/SET YQJ8 LINE2 2
SET ztitle=%TIME%: WINFOLD LINE2 3
TITLE %TIME% _*.* IN WINFOLD LINE2 4
TITLE %TIME% %%ZDATE1%% YQJ25 LINE2 5
TITLE %TIME% FINISHED. PRESS ANY KEY TO SHUTDOWN ... LINE2 6
Код:
Copy-Item $env:windir\_61.bat -d $env:temp\_61.bat
(gc $env:windir\_61.bat) | foreach -Begin {$lc = 1} -Process {
$_ -replace "LINE2 \d*", "LINE2 $lc";
$lc += 1
} | Out-File -Encoding Ascii $env:windir\_61.bat
Я ожидаю, что это займет менее 984 миллисекунд. Это займет 984 миллисекунды. Вы можете придумать что-нибудь, чтобы ускорить это?
1 ответ
Ключ к повышению производительности в коде PowerShell (если не считать встраивания кода C#, скомпилированного по требованию с Add-Type
, который может или не может помочь) заключается в:
- избегать использования командлетов и конвейера в целом,
- особенно вызов блока скрипта (
{...}
) для каждого входного объекта конвейера, например, сForEach-Object
,
- особенно вызов блока скрипта (
- Чтобы избежать конвейера, требуется непосредственное использование типов платформы.NET в качестве альтернативы командлетам.
- если возможно, используйте
switch
операторы для массива или построчная обработка файла -switch
заявления обычно превосходятforeach
петли.
Чтобы быть понятным: конвейер и командлеты дают явные преимущества, поэтому избегать их следует только в том случае, если необходима оптимизация производительности.
В вашем случае следующий код, который объединяет switch
Оператор с прямым использованием.NET Framework для файлового ввода-вывода, кажется, обеспечивает лучшую производительность - обратите внимание, что входной файл считывается в память в целом, как массив строк, и копия этого массива с измененными строками создается до того, как записывается обратно во входной файл:
$file = "$env:temp\_61.bat" # must be a *full* path.
$lc = 0
$updatedLines = & { switch -Regex -File $file {
'^(.*? LINE2 )\d+(.*)$' { $Matches[1] + ++$lc + $Matches[2] }
default { ++$lc; $_ } # pass non-matching lines through
} }
[IO.File]::WriteAllLines($file, $updatedLines, [Text.Encoding]::ASCII)
Замечания:
Вложение
switch
заявление в& { ... }
Это неясная оптимизация производительности, объясненная в этом ответе.Если чувствительного к регистру соответствия достаточно, как показано в примере ввода, вы можете немного улучшить производительность, добавив
-CaseSensitive
вариант кswitch
команда.
В моих тестах (см. Ниже) это обеспечило более чем четырехкратное повышение производительности Windows PowerShell по сравнению с вашей командой.
Вот сравнение производительности через Time-Command
функция:
Сравниваемые команды:
switch
команда сверху.Немного упрощенная версия вашей собственной команды.
Альтернатива PowerShell Core v6.1 +, использующая
-replace
оператор с массивом строк в качестве LHS и блоком сценария в качестве выражения замены.
Вместо файла с 6 строками используется файл с 6 000 строками. 100 прогонов усредняются. Эти параметры легко настроить.
# Sample file content (6 lines)
$fileContent = @'
TITLE %TIME% NO "%zmyapps1%\*.*" ARCHIVE ATTRIBUTE LINE2 1243
TITLE %TIME% DOC/SET YQJ8 LINE2 1887
SET ztitle=%TIME%: WINFOLD LINE2 2557
TITLE %TIME% _*.* IN WINFOLD LINE2 2597
TITLE %TIME% %%ZDATE1%% YQJ25 LINE2 3672
TITLE %TIME% FINISHED. PRESS ANY KEY TO SHUTDOWN ... LINE2 4922
'@
# Determine the full path to a sample file.
# NOTE: Using the *full* path is a *must* when calling .NET methods, because
# the latter generally don't see the same working dir. as PowerShell.
$file = "$PWD/test.bat"
# Create the sample file with the sample content repeated N times.
$repeatCount = 1000 # -> 6,000 lines
[IO.File]::WriteAllText($file, $fileContent * $repeatCount)
# Warm up the file cache and count the lines.
$lineCount = [IO.File]::ReadAllLines($file).Count
# Define the commands to compare as an array of scriptblocks.
$commands =
{ # switch -Regex -File + [IO.File]::Read/WriteAllLines()
$i = 0
$updatedLines = & { switch -Regex -File $file {
'^(.*? LINE2 )\d+(.*)$' { $Matches[1] + ++$i + $Matches[2] }
default { ++$lc; $_ }
} }
[IO.File]::WriteAllLines($file, $updatedLines, [text.encoding]::ASCII)
},
{ # Get-Content + -replace + Set-Content
(Get-Content $file) | ForEach-Object -Begin { $i = 1 } -Process {
$_ -replace "LINE2 \d*", "LINE2 $i"
++$i
} | Set-Content -Encoding Ascii $file
}
# In PS Core v6.1+, also test -replace with a scriptblock operand.
if ($PSVersionTable.PSVersion.Major -ge 6 -and $PSVersionTable.PSVersion.Minor -ge 1) {
$commands +=
{ # -replace with scriptblock + [IO.File]::Read/WriteAllLines()
$i = 0
[IO.File]::WriteAllLines($file,
([IO.File]::ReadAllLines($file) -replace '(?<= LINE2 )\d+', { (++$i) }),
[text.encoding]::ASCII
)
}
} else {
Write-Warning "Skipping -replace-with-scriptblock command, because it isn't supported in this PS version."
}
# How many runs to average.
$runs = 100
Write-Verbose -vb "Averaging $runs runs with a $lineCount-line file of size $('{0:N2} MB' -f ((Get-Item $file).Length / 1mb))..."
Time-Command -Count $runs -ScriptBlock $commands
Вот примерные результаты с моего компьютера с Windows 10 (абсолютные тайминги не важны, но, надеюсь, относительная производительность покажет в Factor
колонка несколько представительная); используемая версия PowerShell Core v6.2.0-preview.4
# Windows 10, Windows PowerShell v5.1
WARNING: Skipping -replace-with-scriptblock command, because it isn't supported in this PS version.
VERBOSE: Averaging 100 runs with a 6000-line file of size 0.29 MB...
Factor Secs (100-run avg.) Command
------ ------------------- -------
1.00 0.108 # switch -Regex -File + [IO.File]::Read/WriteAllLines()...
4.22 0.455 # Get-Content + -replace + Set-Content...
# Windows 10, PowerShell Core v6.2.0-preview 4
VERBOSE: Averaging 100 runs with a 6000-line file of size 0.29 MB...
Factor Secs (100-run avg.) Command
------ ------------------- -------
1.00 0.101 # switch -Regex -File + [IO.File]::Read/WriteAllLines()…
1.67 0.169 # -replace with scriptblock + [IO.File]::Read/WriteAllLines()…
4.98 0.503 # Get-Content + -replace + Set-Content…