如何在PowerShell 5.0/7.0中实现foreach并行执行以提升CSV检索效率
PowerShell 并行统计CSV关键词匹配次数方案
现有代码性能问题说明
- 重复读取文件:每个关键词都会遍历所有CSV文件一次,N个关键词就会产生N倍的IO开销
- 频繁小IO写入:循环内多次调用
Add-Content,产生大量碎片化IO操作 - 串行执行:未利用CPU多核能力,大量CPU密集的字符串匹配操作排队执行
PowerShell 5.0 实现方案
PowerShell 5.0无内置并行语法,采用RunspacePool实现高性能并行,核心逻辑改为每个文件只读取1次,一次性统计所有关键词的匹配次数,减少IO开销:
$dir = "C:\User\Count\Export" $outputPath = "C:\User\Count\Result.csv" $searchWords = 'word1','word2','word3','word4','word5','word6' $csvFiles = Get-ChildItem -Path $dir -Filter *.csv # 初始化结果表:Key为关键词,Value为[文件计数数组, 总和] $result = [ordered]@{} foreach ($word in $searchWords) { $result[$word] = @(,0 * $csvFiles.Count + 0) } # 配置RunspacePool $threadCount = [Environment]::ProcessorCount $runspacePool = [RunspaceFactory]::CreateRunspacePool(1, $threadCount) $runspacePool.Open() $jobs = New-Object System.Collections.Generic.List[PSObject] # 提交并行任务:每个文件对应1个任务 for ($fileIdx = 0; $fileIdx -lt $csvFiles.Count; $fileIdx++) { $file = $csvFiles[$fileIdx] $ps = [PowerShell]::Create().AddScript({ param($fileIndex, $filePath, $searchWords) $content = Get-Content -Path $filePath -Raw $counts = foreach ($word in $searchWords) { [regex]::Matches($content, [regex]::Escape($word)).Count } return @{ FileIndex = $fileIndex Counts = $counts } }).AddArgument($fileIdx).AddArgument($file.FullName).AddArgument($searchWords) $ps.RunspacePool = $runspacePool $jobs.Add(@{ PS = $ps AsyncResult = $ps.BeginInvoke() }) } # 收集任务结果 foreach ($job in $jobs) { $taskResult = $job.PS.EndInvoke($job.AsyncResult) $job.PS.Dispose() $fileIdx = $taskResult.FileIndex $counts = $taskResult.Counts for ($wordIdx = 0; $wordIdx -lt $searchWords.Count; $wordIdx++) { $word = $searchWords[$wordIdx] $result[$word][$fileIdx] = $counts[$wordIdx] $result[$word][-1] += $counts[$wordIdx] } } # 清理RunspacePool $runspacePool.Close() $runspacePool.Dispose() # 一次性生成输出内容写入文件 $outputLines = New-Object System.Collections.Generic.List[string] $header = "关键词," + ($csvFiles.Name -join ",") + ",总和" $outputLines.Add($header) foreach ($word in $searchWords) { $line = "$word," + ($result[$word] -join ",") $outputLines.Add($line) } Set-Content -Path $outputPath -Value $outputLines -Encoding UTF8
PowerShell 7.0+ 实现方案
PowerShell 7内置ForEach-Object -Parallel并行语法,实现更简洁,性能和RunspacePool相当:
$dir = "C:\User\Count\Export" $outputPath = "C:\User\Count\Result.csv" $searchWords = 'word1','word2','word3','word4','word5','word6' $csvFiles = Get-ChildItem -Path $dir -Filter *.csv # 并行处理所有文件 $fileResults = $csvFiles | ForEach-Object -Parallel { $searchWords = $using:searchWords $content = Get-Content -Path $_.FullName -Raw $counts = foreach ($word in $searchWords) { [regex]::Matches($content, [regex]::Escape($word)).Count } return $counts } -ThrottleLimit ([Environment]::ProcessorCount) # 汇总结果 $result = [ordered]@{} foreach ($word in $searchWords) { $result[$word] = @(,0 * $csvFiles.Count + 0) } for ($fileIdx = 0; $fileIdx -lt $csvFiles.Count; $fileIdx++) { $counts = $fileResults[$fileIdx] for ($wordIdx = 0; $wordIdx -lt $searchWords.Count; $wordIdx++) { $word = $searchWords[$wordIdx] $result[$word][$fileIdx] = $counts[$wordIdx] $result[$word][-1] += $counts[$wordIdx] } } # 写入结果 $outputLines = @("关键词," + ($csvFiles.Name -join ",") + ",总和") foreach ($word in $searchWords) { $outputLines += "$word," + ($result[$word] -join ",") } Set-Content -Path $outputPath -Value $outputLines -Encoding UTF8
性能对比:相比原始串行代码,两种并行方案性能可提升3~8倍,提升幅度取决于CPU核心数和文件总量
内容的提问来源于stack exchange,提问作者Dragan Milovac
相关产品推荐
相关产品推荐

