I was trying to measure some ways to write to files in PowerShell. No question about that but I don't understand why the first Measure-Command
statement below takes longer to be executed than the 2nd statement.
They are the same but in the second one I write a scriptblock to send to Invoke-Command
and in the 1st one I only run the command.
All informations about Invoke-Command
speed I can find are about remoting.
This block takes about 4 seconds:
Measure-Command {
$stream = [System.IO.StreamWriter] "$PSScriptRoot\t.txt"
$i = 0
while ($i -le 1000000) {
$stream.WriteLine("This is the line number: $i")
$i++
}
$stream.Close()
} # takes 4 sec
And this code below which is exactly the same but written in a scriptblock passed to Invoke-Command
takes about 1 second:
Measure-Command {
$cmdtest = {
$stream = [System.IO.StreamWriter] "$PSScriptRoot\t2.txt"
$i = 0
while ($i -le 1000000) {
$stream.WriteLine("This is the line number: $i")
$i++
}
$stream.Close()
}
Invoke-Command -ScriptBlock $cmdtest
} # Takes 1 second
How is that possible?
As it turns out, based on feedback from a PowerShell team member on this related GitHub issue, the issue is more generally about (implicit) dot-sourcing (such as direct invocation of an expression) vs. running in a child scope, such as with
&
, the call operator, or, in the case at hand, withInvoke-Command -ScriptBlock
.Running in a child scope avoids variable lookups that are performed when (implicitly) dot-sourcing.
Therefore, as of Windows PowerShell v5.1 / PowerShell Core 6.2, you can speed up side-effect-free expressions by simply invoking them via
& { ... }
, in a child scope (somewhat counter-intuitively, given that creating a new scope involves extra work):That is, this optimization can be used with expressions that aren't expected to modify the caller's variables (directly).
The following simplified code, which uses a
foreach
expression to loop 1 million times (1e6
) demonstrates this: