【问题标题】:How can I make Powershell parse XML faster or further optimize my script?如何让 Powershell 更快地解析 XML 或进一步优化我的脚本?
【发布时间】:2012-07-01 19:00:33
【问题描述】:

我有一个包含 700 万个 XML 文件的设置,大小从几 KB 到几 MB 不等。总而言之,它大约有 180GB 的 XML 文件。我需要执行的工作是分析每个 XML 文件并确定该文件是否包含字符串<ref>,以及是否不将其从当前包含的 Chunk 文件夹中移出到 Referenceless 文件夹中。

我创建的脚本运行良好,但就我的目的而言它非常慢。它计划在大约 24 天内完成对所有 700 万个文件的分析,以每秒大约 3 个文件的速度进行。我可以在我的脚本中更改什么以提高性能吗?

另外,更复杂的是,我在服务器上没有正确的权限来运行 .PS1 文件,因此脚本需要能够在一个命令中从 PowerShell 运行。如果我有权限,我会设置权限。

# This script will iterate through the Chunk folders, removing pages that contain no 
# references and putting them into the Referenceless folder.

# Change this variable to start the program on a different chunk. This is the first   
# command to be run in Windows PowerShell. 
$chunknumber = 1
#This while loop is the second command to be run in Windows PowerShell. It will stop after completing Chunk 113.
while($chunknumber -le 113){
#Jumps the terminal to the correct folder.
cd C:\Wiki_Pages
#Creates an index for the chunk being worked on.
$items = Get-ChildItem -Path "Chunk_$chunknumber"
echo "Chunk $chunknumber Indexed"
#Jumps to chunk folder.
cd C:\Wiki_Pages\Chunk_$chunknumber
#Loops through the index. Each entry is one of the pages.
foreach ($page in $items){
#Creates a variable holding the page's content.
$content = Get-Content $page
#If the page has a reference, then it's echoed.
if($content | Select-String "<ref>" -quiet){echo "Referenced!"}
#if the page doesn't have a reference, it's copied to Referenceless then deleted.
else{
Copy-Item $page C:\Wiki_Pages\Referenceless -force
Remove-Item $page -force
echo "Moved to Referenceless!"
}
}
#The chunk number is increased by one and the cycle continues.
$chunknumber = $chunknumber + 1
}

我对PowerShell知之甚少,昨天是我第一次打开程序。

【问题讨论】:

  • Get-Content 是性能杀手。查看 .net 流式阅读器。它将显着提高您的表现。

标签: xml windows performance optimization powershell


【解决方案1】:

PowerShell 管道可能比本机系统调用慢得多。

PowerShell: pipeline performance

在本文中,在 PowerShell 上执行的两个等效命令和一个经典的 windows 命令提示符之间执行性能测试。

PS> grep [0-9] numbers.txt | wc -l > $null
CMD> cmd /c "grep [0-9] numbers.txt | wc -l > nul"

这是它的输出示例。

PS C:\temp> 1..5 | % { .\perf.ps1 ([Math]::Pow(10, $_)) }

10 iterations

   30 ms  (   0 lines / ms)  grep in PS
   15 ms  (   1 lines / ms)  grep in cmd.exe

100 iterations

   28 ms  (   4 lines / ms)  grep in PS
   12 ms  (   8 lines / ms)  grep in cmd.exe

1000 iterations

  147 ms  (   7 lines / ms)  grep in PS
   11 ms  (  89 lines / ms)  grep in cmd.exe

10000 iterations

 1347 ms  (   7 lines / ms)  grep in PS
   13 ms  ( 786 lines / ms)  grep in cmd.exe

100000 iterations

13410 ms  (   7 lines / ms)  grep in PS
   22 ms  (4580 lines / ms)  grep in cmd.exe

编辑:这个问题的原始答案提到了管道性能以及其他一些建议。为了使这篇文章简洁明了,我删除了与管道性能实际上没有任何关系的其他建议。

【讨论】:

  • 首先,非常感谢您的回答。我无法让 grep 在我的 Powershell 中运行,它只是说这是一个未知命令。环顾四周,显然别名可能是哪里,但信息似乎矛盾,我不知道如何去哪里工作。您是对的,创建变量然后通过管道传递变量效率低下,但是在对其进行测试之后,创建变量和不创建变量之间的速度几乎没有差异。管道性能很有意思,我看看能不能改代码把它去掉。
  • 是的,这是管道过程。在 100 个文件的控制组上,前一个脚本耗时 8 秒,而无管道脚本耗时约 600 毫秒。
  • 有趣。尝试使用 findstr 本机调用。我已经更新了我的答案。
  • findstr 大约需要 3 秒来处理 100 个文件,我的非管道方法大约需要 1 秒来处理相同的文件。它们与我以前使用的文件不同。此外,findstr 未能正确搜索所有文件,出于某种原因,即使所有文件都未能包含字符串 &lt;ref&gt;,它也没有移动 100 个文件中的 7 个。澄清一下,我的方法是:if(Select-String $page -pattern "&lt;ref&gt;" -quiet){echo "Referenced!"}
  • 奇怪,也许 包含换行符或其他不可打印的字符? findstr 也支持正则表达式,因此您可能想尝试一下:msdn.microsoft.com/en-us/subscriptions/…。我不确定我是否理解您的解决方案。从您在上一条评论中所说的来看,您似乎仍在 PowerShell 脚本的范围内使用 Select-String?
【解决方案2】:

您需要将-ReadCount 0 参数添加到您的Get-Content 命令中以加快它们的速度(这很有帮助)。我从 this 伟大的文章中学到了这个技巧,该文章显示在整个文件的内容上运行 foreach 比尝试通过管道解析它更快。

此外,您可以使用Set-ExecutionPolicy Bypass -Scope Process 在当前 Powershell 会话中运行脚本,无需额外权限!

【讨论】:

    【解决方案3】:

    在开始优化之前,您需要确定需要优化的确切位置。您是否受 I/O 限制(读取每个文件需要多长时间)?内存受限(可能不是)? CPU 受限(搜索内容的时间)?

    你说这些是 XML 文件;您是否测试过将文件读入 XML 对象(而不是纯文本),并通过 XPath 定位 &lt;ref&gt; 节点?然后你会有:

    $content = [xml](Get-Content $page)
    #If the page has a reference, then it's echoed.
    if($content.SelectSingleNode("//ref") -quiet){echo "Referenced!"}
    

    如果您有空闲的 CPU、内存和 I/O 资源,您可能会通过并行搜索多个文件看到一些改进。请参阅this discussion 并行运行多个作业。显然你不能同时运行大量,但是通过一些测试你可以找到最佳位置(可能在 3-5 附近)。 foreach ($page in $items){ 中的所有内容都将成为该作业的脚本块。

    【讨论】:

    • 但是将文件解析为 XML 肯定比简单的“IndexOf”慢很多。无论如何,您是对的,需要指定“优化”的含义。
    • 我没有任何大型 XML 文件可供尝试,但需要进行测试以查看是否是这种情况。解析 XML 可能需要更长的时间,但对 ref 节点的搜索可能比 IndexOf 所需的线性搜索更快。在某些情况下,这可能是净收益。
    • 出于好奇,我用我这里的一个 48K XML 文件进行了测试,XML 方法与内容 |select-string 出现的时间非常不同。我第一次使用 XML 方法运行时,花了 30 毫秒。随后的执行(包括将文件从 RAMdisk 读取到 XML 对象中)运行了 11-19 毫秒。使用 select-string 方法(同样,每次都从 RAMdisk 读取),24 毫秒似乎是最低的,有些运行高达 46 毫秒。在这两种情况下,我要查找的节点都位于文件末尾。
    【解决方案4】:

    我会尝试使用 Start-Job cmdlet 一次解析 5 个文件。有很多关于 PowerShell Jobs 的优秀文章。如果由于某种原因没有帮助,并且您遇到 I/O 或实际资源瓶颈,您甚至可以使用 Start-Job 和 WinRM 在其他机器上启动工作程序。

    【讨论】:

      猜你喜欢
      • 2022-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-31
      • 1970-01-01
      • 2020-04-12
      • 1970-01-01
      相关资源
      最近更新 更多