performance - 迭代一个 windows ascii 文本文件,找到 {LINE2 1-9999} 的所有实例替换为 {LINE2 "line number the code is on"}。覆盖。快点?
问题描述
此代码有效。我只是想看看有人能让它工作多快。
备份您的 Windows 10 批处理文件,以防出现问题。查找字符串 {LINE2 1-9999} 的所有实例并替换为 {LINE2 "line number the code is on"}。覆盖,编码为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 毫秒。你能想出什么来加速它吗?
解决方案
在 PowerShell 代码中提高性能的关键(缺少嵌入按需编译的 C# 代码Add-Type
,这可能有帮助,也可能没有帮助)是:
- 一般避免使用 cmdlet 和管道,
- 特别是为每个管道输入对象调用脚本块 (
{...}
),例如 withForEach-Object
。
- 特别是为每个管道输入对象调用脚本块 (
- 避免使用管道需要直接使用 .NET 框架类型来替代cmdlet。
- 如果可行,请使用
switch
语句进行数组或逐行文件处理-switch
语句通常优于foreach
循环。
需要明确的是:管道和 cmdlet 提供了明显的好处,因此只有在必须优化性能时才应该避免使用它们。
在您的情况下,以下代码将switch
语句与直接使用 .NET 框架进行文件 I/O相结合似乎提供了最佳性能 - 请注意,输入文件作为一个整体读入内存,作为一个行数组,并在将其写回输入文件之前创建该数组的副本以及修改后的行:
$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 中提供了 4 倍以上的性能改进。
这是通过函数进行的性能比较:Time-Command
比较的命令是:
上面的
switch
命令。您自己的命令的略微简化的版本。
一种 PowerShell Core v6.1+ 替代方案,它使用
-replace
带有行数组的运算符作为 LHS,脚本块作为替换表达式。
使用 6,000 行文件代替 6 行示例文件。平均运行 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…
推荐阅读
- python - 在 Python 中计算换行符的问题
- c - 如何以有效(循环)的方式显示循环队列的元素?
- java - 一维数组的嵌套增强 for 循环
- java - OutOfMemoryError after executing a ThreadPoolExecutor many times
- docker - 需要在启动时在非 root 容器的 /dev 路径中创建文件
- c++ - 如何将成员函数作为参数传递?
- dns - 使用所有独立的提供商托管域、电子邮件和网站
- racket - 球拍 - 以指数为底,不相乘
- ffmpeg - ffmpeg libfdk-aac 打开编码器错误并显示编码器时基未设置
- c# - 房间体积计算 - 奇怪的行为和获得房间实体的最准确方法是什么?