性能测试PowerShell整数除法的两种方法

发布于 2024-12-26 16:49:38 字数 1088 浏览 2 评论 0原文

Microsoft technet 建议 [Math]::Floor([int]$a / [int]$b) 用于整数除法。我相信 [int][Math]::Floor($a / $b) 由于减少了一次强制转换操作,因此可读性更强,性能也更高。 我已经证明这两种方法是等效的。但是,我无法得到一致的结果。我的方法包括重复这两种方法 10,000 次,并使用 Measure-Command cmdlet。然而,不能构建一个测试,其中一个测试重复地比另一个测试表现更好。我的代码如下:

Write-Host
$loopLength = 10000

$runtime = Measure-Command {
    1..$loopLength | ForEach-Object {
        Foreach ($divisor in 2,3,5,7) {
            [Math]::Floor([int]$_ / [int]$divisor) > $null
        }
    }
}

"Double Cast: $($runtime.TotalMilliSeconds)"

$runtime = Measure-Command {
    1..$loopLength | ForEach-Object {
        Foreach ($divisor in 2,3,5,7) {
            [int][Math]::Floor($_ / $divisor) > $null           
        }
    }
}
"Single Cast: $($runtime.TotalMilliSeconds)"

如何修改我的代码,以便获得一致的结果,证明一种方法比另一种方法更好。

Microsoft technet suggests [Math]::Floor([int]$a / [int]$b) for integer division. I believe that [int][Math]::Floor($a / $b) is both more readable as well as more performant due to one less cast operation. I have proven both methods equivalent. However, I cannot get consistent results. My methodology involves repeating both methodologies 10,000 times and measuring the results with the Measure-Command cmdlet. However cannot constuct a test where one test performs better than another test repeatedly. My code is below:

Write-Host
$loopLength = 10000

$runtime = Measure-Command {
    1..$loopLength | ForEach-Object {
        Foreach ($divisor in 2,3,5,7) {
            [Math]::Floor([int]$_ / [int]$divisor) > $null
        }
    }
}

"Double Cast: $($runtime.TotalMilliSeconds)"

$runtime = Measure-Command {
    1..$loopLength | ForEach-Object {
        Foreach ($divisor in 2,3,5,7) {
            [int][Math]::Floor($_ / $divisor) > $null           
        }
    }
}
"Single Cast: $($runtime.TotalMilliSeconds)"

How do I modify my code so I get consistent results that prove one method is better than another.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

神魇的王 2025-01-02 16:49:41

对于我来说,这个性能优化并不是很重要。 PowerShell 本身比编译语言慢得多,因此如果您确实需要性能,请使用编译语言或使用 Add-Type 编译代码。

除此之外,如果您测试性能,则需要最少的其他可能改变结果的代码。 Foreach-Object 本身增加了其自身的复杂性。这就是为什么我建议改用 foreach 语句。

令人惊讶的是,我的机器上的结果有时相反。

[76]: $loopLength = 100000
[77]:
[77]: $runtime = Measure-Command {
>>     foreach($i in 1..$loopLength) {
>>         Foreach ($divisor in 2,3,5,7) {
>>             [Math]::Floor([int]$i / [int]$divisor) > $null
>>         }
>>     }
>> }
>>
[78]: "Double Cast: $($runtime.TotalMilliSeconds)"
Double Cast: 16294.3328
[79]:
[79]: $runtime = Measure-Command {
>>     foreach($i in 1..$loopLength) {
>>         Foreach ($divisor in 2,3,5,7) {
>>             [int][Math]::Floor($i / $divisor) > $null
>>         }
>>     }
>> }
>> "Single Cast: $($runtime.TotalMilliSeconds)"
>>
Single Cast: 15924.3836

As for me, this performance optimization is not really important. PowerShell itself is far more slower than compiled langugages, so if you really need performance, use compiled languages or compile your code with Add-Type.

Besides that - if you test performance, you need minimal other code that could change the results. Foreach-Object itself adds its own complexity. That's why I would advice to use foreach statement instead.

Surprisingly, the results on my machine are sometimes opposite..

[76]: $loopLength = 100000
[77]:
[77]: $runtime = Measure-Command {
>>     foreach($i in 1..$loopLength) {
>>         Foreach ($divisor in 2,3,5,7) {
>>             [Math]::Floor([int]$i / [int]$divisor) > $null
>>         }
>>     }
>> }
>>
[78]: "Double Cast: $($runtime.TotalMilliSeconds)"
Double Cast: 16294.3328
[79]:
[79]: $runtime = Measure-Command {
>>     foreach($i in 1..$loopLength) {
>>         Foreach ($divisor in 2,3,5,7) {
>>             [int][Math]::Floor($i / $divisor) > $null
>>         }
>>     }
>> }
>> "Single Cast: $($runtime.TotalMilliSeconds)"
>>
Single Cast: 15924.3836
樱花细雨 2025-01-02 16:49:41

通过使用良好的老式 For 循环,我还能够将 ForEach 示例的时间缩短约 7 倍;尽管最快和最慢的差距仍然只有20毫秒左右。

然而,您还应该注意非整数输入的两个转换位置的细微操作差异:

  • [Math]::Floor([int]17.2 / [int]1.1) = 17
  • [ int][Math]::Floor(17.2 / 1.1) = 15

平均 For 循环代码:

$Spins       = 100
$loopLength  = 10000
$Denominator = 2,3,5,7

$runtime = Measure-Command {
   for($s=0;$s -lt $Spins;$s++){
    for($Numerator=1;$Numerator -le $loopLength; $Numerator++){
        For($i=0;$i -lt 4;$i++) {
            $null=[Math]::Floor([int]$Numerator / [int]$Denominator[$i])
        }
    }
   }
}

"Double Cast: $($runtime.TotalMilliSeconds/$Spins)"

$runtime = Measure-Command {
   for($s=0;$s -lt $Spins;$s++){
    for($Numerator=1;$Numerator -le $loopLength; $Numerator++){
        For($i=0;$i -lt 4;$i++) {
            $null=[int][Math]::Floor($Numerator / $Denominator[$i])
        }
    }
   }
}
"Single Cast: $($runtime.TotalMilliSeconds/$Spins)"

add-type -TypeDefinition @'
public class intOps{
   public static ulong div(ulong a, ulong b){
      return a/b;
   }
}
'@
$runtime = Measure-Command {
   for($s=0;$s -lt $Spins;$s++){
    for($Numerator=1;$Numerator -le $loopLength; $Numerator++){
        For($i=0;$i -lt 4;$i++) {
            $null=[intOps]::div($Numerator,$Denominator[$i])
        }
    }
   }
}
"C# Cast: $($runtime.TotalMilliSeconds/$Spins)"

I was also able to cut the time by about a factor of 7 from the ForEach example by just using good old-fashioned For loops; although the difference between the fastest and slowest was still only around 20 milliseconds.

You should however also note the slight operational difference in the two casting locations for non-integer inputs:

  • [Math]::Floor([int]17.2 / [int]1.1) = 17
  • [int][Math]::Floor(17.2 / 1.1) = 15

Averaged For-Loop Code:

$Spins       = 100
$loopLength  = 10000
$Denominator = 2,3,5,7

$runtime = Measure-Command {
   for($s=0;$s -lt $Spins;$s++){
    for($Numerator=1;$Numerator -le $loopLength; $Numerator++){
        For($i=0;$i -lt 4;$i++) {
            $null=[Math]::Floor([int]$Numerator / [int]$Denominator[$i])
        }
    }
   }
}

"Double Cast: $($runtime.TotalMilliSeconds/$Spins)"

$runtime = Measure-Command {
   for($s=0;$s -lt $Spins;$s++){
    for($Numerator=1;$Numerator -le $loopLength; $Numerator++){
        For($i=0;$i -lt 4;$i++) {
            $null=[int][Math]::Floor($Numerator / $Denominator[$i])
        }
    }
   }
}
"Single Cast: $($runtime.TotalMilliSeconds/$Spins)"

add-type -TypeDefinition @'
public class intOps{
   public static ulong div(ulong a, ulong b){
      return a/b;
   }
}
'@
$runtime = Measure-Command {
   for($s=0;$s -lt $Spins;$s++){
    for($Numerator=1;$Numerator -le $loopLength; $Numerator++){
        For($i=0;$i -lt 4;$i++) {
            $null=[intOps]::div($Numerator,$Denominator[$i])
        }
    }
   }
}
"C# Cast: $($runtime.TotalMilliSeconds/$Spins)"
合约呢 2025-01-02 16:49:41

TechNet 上的示例有点愚蠢,因为数字已经是 System.Int32 类型。看一下这个示例:

PS C:\Users\andy> [math]::floor( 100 / 26 ).GetType().Fullname
System.Double
PS C:\Users\andy> (100).GetType().FullName
System.Int32
PS C:\Users\andy> [int].FullName
System.Int32

因此完全没有必要将 [int] 放在 Floor 方法参数前面,因为它们已经是 System.Int32 类型。

另外,无论如何,您都不希望将返回的 System.Double 转换为 Int32,因为返回值可能比 Int32 更大。抓住。例如:

PS C:\Users\andy> [int][math]::floor( ([int]::MaxValue + 1) / 1 )
Cannot convert value "2147483648" to type "System.Int32". Error: "Value was either too large or too small for an Int32."

就性能而言,速度差异可以忽略不计。无论您是否愿意,PowerShell 引擎都会在幕后执行大量类型适应和强制转换...它是这样设计的,因此系统管理员不必过多担心 int、double、小数等...数字就是数字吧? ;-) 例如:

[Math]::Floor("123")
# This outputs 123 as System.Double.

这甚至无法在 C# 中编译。 PowerShell 运行时执行必要的转换以满足 Floor 方法签名。

另一个例子:

"2" / "1"
# This outputs 2 as System.Int32.

字符串无法进行除法,但 PowerShell 引擎会在后台进行转换,以便您完成这项工作。

以下是我的机器的性能结果:

function Get-SingleCastTime {
    $runtime = Measure-Command {
        1..10000 | ForEach-Object {
            Foreach ($divisor in 2,3,5,7) {
                [int][Math]::Floor($_ / $divisor) > $null          
            }
        }
    }
    "Single Cast: $($runtime.TotalMilliSeconds)"
}

function Get-DoubleCastTime {
    $runtime = Measure-Command {
        1..10000 | ForEach-Object {
            Foreach ($divisor in 2,3,5,7) {
                [Math]::Floor([int]$_ / [int]$divisor) > $null
            }
        }
    }

    "Double Cast: $($runtime.TotalMilliSeconds)"
}

Get-SingleCastTime
#Single Cast: 614.6537

Get-DoubleCastTime
#Double Cast: 545.2668

Get-DoubleCastTime
#Double Cast: 514.2103

Get-SingleCastTime
#Single Cast: 526.9188

The example on TechNet is kinda silly because the numbers are already of type System.Int32. Take a look at this example:

PS C:\Users\andy> [math]::floor( 100 / 26 ).GetType().Fullname
System.Double
PS C:\Users\andy> (100).GetType().FullName
System.Int32
PS C:\Users\andy> [int].FullName
System.Int32

So it's completely unnecessary to put [int] in front of the Floor method parameters because they are already of type System.Int32.

Also, you wouldn't want to cast the returned System.Double to an Int32 anyway because the return value could be larger than an Int32 can hold. For example:

PS C:\Users\andy> [int][math]::floor( ([int]::MaxValue + 1) / 1 )
Cannot convert value "2147483648" to type "System.Int32". Error: "Value was either too large or too small for an Int32."

As for performance, the difference in speed is negligible. The PowerShell engine performs a lot of type adaptation and coercion behind the scenes whether you want it to or not... It was designed this way so system admins didn't have to worry too much about int's, double's, decimals etc... A number is a number right? ;-) For example:

[Math]::Floor("123")
# This outputs 123 as System.Double.

This wouldn't even compile in C#. The PowerShell runtime performs the necessary casting to meet the Floor method signature.

Another example:

"2" / "1"
# This outputs 2 as System.Int32.

Division isn't possible with strings but the PowerShell engine does transformation in the background for you to make this work.

Here are the performance results from my machine:

function Get-SingleCastTime {
    $runtime = Measure-Command {
        1..10000 | ForEach-Object {
            Foreach ($divisor in 2,3,5,7) {
                [int][Math]::Floor($_ / $divisor) > $null          
            }
        }
    }
    "Single Cast: $($runtime.TotalMilliSeconds)"
}

function Get-DoubleCastTime {
    $runtime = Measure-Command {
        1..10000 | ForEach-Object {
            Foreach ($divisor in 2,3,5,7) {
                [Math]::Floor([int]$_ / [int]$divisor) > $null
            }
        }
    }

    "Double Cast: $($runtime.TotalMilliSeconds)"
}

Get-SingleCastTime
#Single Cast: 614.6537

Get-DoubleCastTime
#Double Cast: 545.2668

Get-DoubleCastTime
#Double Cast: 514.2103

Get-SingleCastTime
#Single Cast: 526.9188
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文