【问题标题】:Timing a command's execution in PowerShell在 PowerShell 中计时命令的执行
【发布时间】:2011-03-31 15:07:03
【问题描述】:

是否有一种简单的方法来为 PowerShell 中的命令执行计时,例如 Linux 中的“时间”命令?
我想出了这个:

$s=Get-Date; .\do_something.ps1 ; $e=Get-Date; ($e - $s).TotalSeconds

但我想要一些更简单的东西,比如

time .\do_something.ps1

【问题讨论】:

    标签: powershell time performance-testing


    【解决方案1】:

    一种更受 PowerShell 启发的方式来访问您关心的属性的值:

    $myCommand = .\do_something.ps1
    Measure-Command { Invoke-Expression $myCommand } | Select -ExpandProperty Milliseconds
    
        4
    

    作为Measure-Command 返回一个TimeSpan 对象。

    注意:TimeSpan 对象也有 TotalMilliseconds 作为双精度数(例如上面我的例子中的 4.7322 TotalMilliseconds),这可能对您有用。就像 TotalSeconds、TotalDays 等。

    【讨论】:

      【解决方案2】:

      到目前为止,所有答案都没有达到提问者(和我)通过简单地在命令行开头添加“时间”来为命令计时的愿望。相反,它们都需要将命令括在括号 ({}) 中以生成块。这是一个在 Unix 上更像 time 的简短函数:

      Function time() {
        $command = $args -join ' '
        Measure-Command { Invoke-Expression $command | Out-Default }
      }
      

      【讨论】:

        【解决方案3】:

        是的。

        Measure-Command { .\do_something.ps1 }
        

        请注意,Measure-Command 的一个小缺点是您看不到 stdout 输出。

        [更新,感谢@JasonMArcher] 您可以通过将命令输出通过管道传送到一些写入主机的命令行开关来解决这个问题,例如Out-Default 这样就变成了:

        Measure-Command { .\do_something.ps1 | Out-Default }
        

        查看输出的另一种方法是使用 .NET Stopwatch 类,如下所示:

        $sw = [Diagnostics.Stopwatch]::StartNew()
        .\do_something.ps1
        $sw.Stop()
        $sw.Elapsed
        

        【讨论】:

        • 你也可以看到这样的输出,Measure-Command {ps |出默认}。或者其他任何直接写入主机的东西,这可能有用也可能没用。
        • 我采用了这个解决方案并编写了一个可能对其他人有用的函数。 gist.github.com/2206444 -- 示例:time { ping -n 1 google.com } -Samples 10 将运行命令 10 次并返回平均、最小和最大时间。可以加-Silent吞STDOUT。
        • 我的偏好是将 Measure-Command 的结果分配给一个变量,例如 $t = Measure-Command {<<your command or code block>>} 。试一试,然后在提示符下输入$t 以查看您的结果以及您可以访问的所有属性,例如$t.Milliseconds$t.TotalSeconds 等。然后我们可以写入我们想要的任何输出,例如,@ 987654336@
        • 用什么更快? net.stopwatch,或者测量命令,或者只是比较两个获取日期变量......(我的意思是永久保存在脚本中更有效?)
        • 类似于| Out-Default,也可以使用-OutVariable来存储输出对象,例如$m = Measure-Command { Get-WinEvent -LogName System -MaxEvents 1 -OutVariable events }; $events.Message; $m.TotalSeconds
        【解决方案4】:

        从答案中提到的任何性能测量命令中得出(不正确的)结论。除了查看(自定义)函数或命令的裸调用时间之外,还应考虑许多缺陷。

        Sjoemel 软件

        'Sjoemelsoftware' voted Dutch word of the year 2015
        Sjoemelen是作弊的意思,sjoemelsoftware这个词因大众汽车排放丑闻而应运而生.官方定义是“用于影响测试结果的软件”。

        我个人认为“Sjoemelsoftware”并非总是故意创建以欺骗测试结果,而是可能源于适应类似于如下所示测试用例的实际情况。

        例如,使用列出的性能测量命令Language Integrated Query (LINQ)(1) 通常被认为是完成某事的禁食方法,而且通常是,但肯定不总是!任何人如果测量与原生 PowerShell 命令相比 40 倍或更多的速度增加,则可能是在测量错误或得出错误的结论。

        重点是某些 .Net 类(如 LINQ)使用lazy evaluation(也称为延迟执行(2))。这意味着当将表达式分配给变量时,它几乎立即完成,但实际上它还没有处理任何事情!

        假设您 dot-source 您的 . .\Dosomething.ps1 命令具有 PowerShell 或更复杂的 Linq 表达式(为了便于解释,我已将表达式直接嵌入到 Measure-Command 中):

        $Data = @(1..100000).ForEach{[PSCustomObject]@{Index=$_;Property=(Get-Random)}}
        
        (Measure-Command {
            $PowerShell = $Data.Where{$_.Index -eq 12345}
        }).totalmilliseconds
        864.5237
        
        (Measure-Command {
            $Linq = [Linq.Enumerable]::Where($Data, [Func[object,bool]] { param($Item); Return $Item.Index -eq 12345})
        }).totalmilliseconds
        24.5949
        

        结果很明显,后面的 Linq 命令比第一个 PowerShell 命令快了大约 40 倍。不幸的是,没有那么简单......

        让我们显示结果:

        PS C:\> $PowerShell
        
        Index  Property
        -----  --------
        12345 104123841
        
        PS C:\> $Linq
        
        Index  Property
        -----  --------
        12345 104123841
        

        正如预期的那样,结果是相同的,但如果您仔细观察,您会注意到显示$Linq 结果比显示$PowerShell 结果花费的时间要长得多。
        让我们通过检索结果对象的属性来专门衡量

        PS C:\> (Measure-Command {$PowerShell.Property}).totalmilliseconds
        14.8798
        PS C:\> (Measure-Command {$Linq.Property}).totalmilliseconds
        1360.9435
        

        检索$Linq 对象的属性比检索$PowerShell 对象的属性要长大约90 倍,而这只是一个对象!

        还要注意另一个陷阱,如果你再做一次,某些步骤可能会比以前快很多,这是因为某些表达式已被缓存。

        归根结底,如果您想比较两个函数之间的性能,您将需要在您的用例中实现它们,从一个新的 PowerShell 会话开始,并将您的结论基于完整解决方案的实际性能。

        (1) 有关 PowerShell 和 LINQ 的更多背景和示例,我推荐 tihis 站点:High Performance PowerShell with LINQ
        (2) 我认为两者之间存在细微差别与 惰性评估 的概念类似,结果是在 当需要时 计算出来的,与 延迟执行 是在系统 空闲时计算结果

        【讨论】:

        【解决方案5】:

        这是我编写的一个函数,其工作方式类似于 Unix time 命令:

        function time {
            Param(
                [Parameter(Mandatory=$true)]
                [string]$command,
                [switch]$quiet = $false
            )
            $start = Get-Date
            try {
                if ( -not $quiet ) {
                    iex $command | Write-Host
                } else {
                    iex $command > $null
                }
            } finally {
                $(Get-Date) - $start
            }
        }
        

        来源:https://gist.github.com/bender-the-greatest/741f696d965ed9728dc6287bdd336874

        【讨论】:

        • 问题是“在 PowerShell 中计时命令的执行”。这与使用 Unix 为进程计时有什么关系?
        • 这是我编写的一个 Powershell 函数,它展示了如何自己计算执行时间,而不是使用 Measure-Command 或您可以在 Powershell 中计时执行的各种其他方法之一。如果您阅读了原始问题,他要求提供“类似于 Linux 中的 time 命令”的东西。
        【解决方案6】:

        使用Measure-Command

        例子

        Measure-Command { <your command here> | Out-Host }
        

        Out-Host 的管道允许您查看命令的输出,即 否则由Measure-Command 使用。

        【讨论】:

        • 我认为您的意思是 Measure-Command {&lt;your command } | Out-Host - Out-Host 在脚本块之外
        • @Peter - 它需要在块内,否则 Measure-Command 在输出到控制台之前会消耗输出。
        • 知道了...在这种情况下,您甚至可能不需要管道。它应该只打印结果,除非你将它包装在其他块中......
        • Out-Default 是否可能比 Out-Host 更好,因为它与脚本兼容? jsnover.com/blog/2013/12/07/write-host-considered-harmful
        • 好吧,我尝试了 Out-Default,它在终端中也可以正常工作,那么为什么不总是使用 Out-Default 呢? (抱歉,我没有在脚本中尝试过)
        【解决方案7】:

        您还可以从历史记录中获取最后一条命令,并从其StartExecutionTime 中减去其EndExecutionTime

        .\do_something.ps1  
        $command = Get-History -Count 1  
        $command.EndExecutionTime - $command.StartExecutionTime
        

        【讨论】:

        • 有时间试试这个:Get-History | Group {$_.StartExecutionTime.Hour} | sort Count -desc 按一天中的小时查看您的 PowerShell 使用模式。 :-)
        • +1 能够使用它来找出某件事花了多长时间,即使您在开始时没想到它会花费很长时间,因此您没有想到将其包装在 Measure 中-命令。
        • powershell 有时很棒。
        • 是的,这很棒!我做了一个单线使用:$command = Get-History -Count 1 ; "{0}" -f ($command.EndExecutionTime - $command.StartExecutionTime)
        • 重要说明,我不知道这在 10 年有多真实,但在此评论之前的某个时间点,命令对象有一个“持续时间”字段,因此从两个 FooExecutionTime 字段手动计算不再需要。
        【解决方案8】:

        使用秒表并格式化经过的时间:

        Function FormatElapsedTime($ts) 
        {
            $elapsedTime = ""
        
            if ( $ts.Minutes -gt 0 )
            {
                $elapsedTime = [string]::Format( "{0:00} min. {1:00}.{2:00} sec.", $ts.Minutes, $ts.Seconds, $ts.Milliseconds / 10 );
            }
            else
            {
                $elapsedTime = [string]::Format( "{0:00}.{1:00} sec.", $ts.Seconds, $ts.Milliseconds / 10 );
            }
        
            if ($ts.Hours -eq 0 -and $ts.Minutes -eq 0 -and $ts.Seconds -eq 0)
            {
                $elapsedTime = [string]::Format("{0:00} ms.", $ts.Milliseconds);
            }
        
            if ($ts.Milliseconds -eq 0)
            {
                $elapsedTime = [string]::Format("{0} ms", $ts.TotalMilliseconds);
            }
        
            return $elapsedTime
        }
        
        Function StepTimeBlock($step, $block) 
        {
            Write-Host "`r`n*****"
            Write-Host $step
            Write-Host "`r`n*****"
        
            $sw = [Diagnostics.Stopwatch]::StartNew()
            &$block
            $sw.Stop()
            $time = $sw.Elapsed
        
            $formatTime = FormatElapsedTime $time
            Write-Host "`r`n`t=====> $step took $formatTime"
        }
        

        使用示例

        StepTimeBlock ("Publish {0} Reports" -f $Script:ArrayReportsList.Count)  { 
            $Script:ArrayReportsList | % { Publish-Report $WebServiceSSRSRDL $_ $CarpetaReports $CarpetaDataSources $Script:datasourceReport };
        }
        
        StepTimeBlock ("My Process")  {  .\do_something.ps1 }
        

        【讨论】:

          【解决方案9】:

          简单

          function time($block) {
              $sw = [Diagnostics.Stopwatch]::StartNew()
              &$block
              $sw.Stop()
              $sw.Elapsed
          }
          

          然后可以用作

          time { .\some_command }
          

          您可能需要调整输出

          【讨论】:

          • Measure-Command 隐藏命令输出,所以这个解决方案有时会更好。
          • 这是一个很棒的解决方案,它尊重命令的输出。您也可以在不使用花括号的情况下为简单命令调用它,例如:“time ls”,就像在 Unix 中一样。
          • 我正在使用它来测量函数调用。但是,您最好将参数也添加到调用约定中,如下所示:&amp; $block @Args
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-06-08
          • 1970-01-01
          • 2012-08-03
          • 1970-01-01
          • 2014-11-03
          相关资源
          最近更新 更多