Hey, Scripting Guy! Weekend Scripter: Timing the Windows PowerShell Pipeline

ScriptingGuy1

 

Bookmark and Share

 

Microsoft Scripting Guy Ed Wilson here. I decided to get up early this morning and head out with my laptop, a camera, and my panama hat. My destination? Battery Park. The Scripting Wife and I are in Charleston, South Carolina, for the weekend, and I thought it would be a great time to write some scripts.

The Scripting Wife has a tendency to hibernate on Saturday mornings, and I learned a long time ago to be quiet. A hibernating bear is more safely awakened than a hibernating Scripting Wife. I therefore quietly escaped into the already hot and humid Charleston summer morning, and I went on a scouting mission to find a decent cup of tea. After checking out several different places—each of whom thought that a tea bag filled with what appeared to be crab grass was a fine morning brew—I gave up and went to a little pastry shop that sold beignets and a cup of coffee to go. As it turned out, the beignets were actually good, but the coffee tasted like burned cardboard. I am all for the waste hierarchy; I just do not think it has any place in morning beverages. I finally made it to Battery Park and was accosted by the little furry guy you see just below. I think he wanted one of my beignets, but I was able to snap his picture and still safeguard my breakfast.

Photo of Beignet the Scripting Squirrel

After getting rid of all the powdered sugar from my hands, I opened up my laptop and went to work. I had an idea for improving the performance of my ParseScriptCommands.ps1 script from yesterday, and I wanted to test it out.

I have often heard that Windows PowerShell is inefficient when it comes to using the pipeline, and that it is actually quicker to store stuff in a variable and then iterate through the variable by using the Foreach statement. To be honest, I have not seen this behavior, but I have heard about it and have read about it on several blogs. It actually seems counterintuitive, but hey, who knows.

When my ParseScriptCommands.ps1 script took several minutes to run (I have more than 1,300 scripts in my c:\data\psextras folder), I decided to put this claim to a test. The modified ModifiedParseScriptCommands.ps1 script is shown here.

ModifiedParseScriptCommands.ps1

$errors = $null 
$logpath = “C:\a\commandlog.txt” 
$path = “C:\data\PSExtras” 
$scripts = Get-ChildItem -Path $path -Include *.ps1 -Recurse 
ForEach($s in $scripts) 
{ 
$script = $s.fullname 
$scriptText = get-content -Path $script 
[system.management.automation.psparser]::Tokenize($scriptText, [ref]$errors) | 
Foreach-object -Begin { 
“Processing $script”
| Out-File -FilePath $logPath -Append } ` 
-process { if($_.type -eq “command”) 
{ “`t $($_.content)” | Out-File -FilePath $logpath -Append } } 
} 
#notepad $logpath

Only two lines were changed in the script. The first one stores the results of the Get-ChildItem cmdlet in the $Scripts variable, which is shown here:

$scripts = Get-ChildItem -Path $path -Include *.ps1 -Recurse

The second change moves from the ForEach-Object cmdlet to the ForEach statement. This change is shown here:

ForEach($s in $scripts)

I commented out the line in each script that opens the log in Notepad, and I used my Test-TwoScripts.ps1 script to evaluate the changes. The Test-TwoScripts.ps1 script is shown here.

Test-TwoScripts.ps1

Param( 
[string]$baseLineScript, 
[string]$modifiedScript, 
[int]$numberOfTests = 1, 
[switch]$log 
) #end param 
Function Test-Scripts 
{ 
Param( 
[string]$baseLineScript, 
[string]$modifiedScript 
) #end param 
Measure-Command -Expression { Invoke-Expression -command $baseLineScript } 
Measure-Command -Expression { Invoke-Expression -command $modifiedScript } 
} #end Test-Scripts function 
Function Get-Change($baseLine, $modified) 
{ 
(($baseLine  $modified)/$baseLine)*100 
} #end Get-Change function 
Function Get-TempFile 
{ 
[io.path]::GetTempFileName() 
} #end Get-TempFile function 
# *** Entry Point To Script 
if($log) { $logFile = Get-TempFile } 
$results = $modified = $baseLine = $null 
For($i = 0 ; $i&nbsp ;-le $numberOfTests ; $i++) 
{ 
“Test $i of $numberOfTests”
; start-sleep -m 50 
$results= Test-Scripts -baseLineScript $baseLineScript -modifiedScript $modifiedScript 
$baseLine += $results[0].TotalSeconds 
$modified += $results[1].TotalSeconds 
If($log) 
{ 
“$baseLineScript run $i of $numberOfTests $(get-date)”
>> $logFile 
$results[0] >> $logFile 
“$modifiedScript run $i of $numberOfTests $(get-date)”
>> $logFile 
$results[1] >> $logFile 
} #if $log 
} #for $i 
“Average change over $numberOfTests tests” 
“BaseLine: $baseLineScript average Total Seconds: $($baseLine/$numberOfTests)” 
“Modified: $modifiedScript average Total Seconds: $($modified/$numberOfTests)” 
“Percent Change: ”
+ “{0:N2}” -f (Get-Change -baseLine $baseLine -modified $modified) 
if($log) 
{ 
“Average change over $numberOfTests tests”
>> $logFile 
“BaseLine: $baseLineScript average Total Seconds: $($baseLine/$numberOfTests)”
>> $logFile 
“Modified: $modifiedScript average Total Seconds: $($modified/$numberOfTests)”
>> $logFile 
“Percent Change: ”
+ “{0:N2}” -f (Get-Change -baseLine $baseLine -modified $modified) >> $logFile 
} #if $log 
if($log) { Notepad $logFile }

The results of the change are seen in the following image. The modified script actually took nine seconds longer to run. Therefore, storing the results in a variable rather than using the pipeline caused a 7 percent performance hit.

Image showing results of changed script

Many of the things written about the pipeline are probably referring to Windows PowerShell 1.0, and because of the performance tuning that took place for Windows PowerShell 2.0, they are no longer applicable. However, your environment and your needs may be different. This is where testing comes into play.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send email to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson and Craig Liebendorfer, Scripting Guys

0 comments

Discussion is closed.

Feedback usabilla icon