I've been wondering about the performance impact of functions in PowerShell.
Let's say we want to generate 100.000 random numbers using System.Random.
$ranGen = New-Object System.Random
Executing
for ($i = 0; $i -lt 100000; $i++) {
$void = $ranGen.Next()
}
finishes within 0.19 seconds.
I put the call inside a function
Get-RandomNumber {
param( $ranGen )
$ranGen.Next()
}
Executing
for ($i = 0; $i -lt 100000; $i++) {
$void = Get-RandomNumber $ranGen
}
takes about 4 seconds.
Why is there such a huge performance impact?
Is there a way I can use functions and still get the performance I have with the direct call?
Are there better (more performant) ways of code encapsulation in PowerShell?
a function call is expensive. The way to get around that is to put as much as you can IN the function. take a look at the following ...
$ranGen = New-Object System.Random
$RepeatCount = 1e4
'Basic for loop = {0}' -f (Measure-Command -Expression {
for ($i = 0; $i -lt $RepeatCount; $i++) {
$Null = $ranGen.Next()
}
}).TotalMilliseconds
'Core in function = {0}' -f (Measure-Command -Expression {
function Get-RandNum_Core {
param ($ranGen)
$ranGen.Next()
}
for ($i = 0; $i -lt $RepeatCount; $i++) {
$Null = Get-RandNum_Core $ranGen
}
}).TotalMilliseconds
'All in function = {0}' -f (Measure-Command -Expression {
function Get-RandNum_All {
param ($ranGen)
for ($i = 0; $i -lt $RepeatCount; $i++) {
$Null = $ranGen.Next()
}
}
Get-RandNum_All $ranGen
}).TotalMilliseconds
output ...
Basic for loop = 49.4918
Core in function = 701.5473
All in function = 19.5579
from what i vaguely recall [and can't find again], after a certain number of repeats, the function scriptblock gets JIT-ed ... that seems to be where the speed comes from.
Related
I want multiple data fetching from excel sheet. I am getting error is Index was outside the bounds of the array.
$Data = Read-Host "Enter the count of Datastore"
$ds = "-sm-"
$Range1 = $Worksheetx1.Range("B1","B1048570")
$Search1 = $Range1.find($ds)
$r = $Search1.row
for ($i=1; $i -le $Data; $i++)
{
$Datastore = #()
$Datastore[$i] = $Worksheetx1.Cells.Item($r, 2).Value2
$r = $r+1
}
$Total_Datastore = $Datastore1 + $Datastore2 + $Datastore3 + $Datastore4
$Total_Datastore
The problem resides in this code:
for ($i=1; $i -le $Data; $i++)
{
$Datastore = #()
$Datastore[$i] = $Worksheetx1.Cells.Item($r, 2).Value2
$r = $r+1
}
You're creating an empty array $Datastore = #(), and try to store data in the second index ($i=1, array index starts at zero, therefore index two). This causes an IndexOutOfRangeException.
Also $Total_Datastore = $Datastore1 + $Datastore2 + $Datastore3 + $Datastore4 doesn't make sense, since $Datastore1 (2,3 and 4) aren't defined anywhere.
Try:
# Only integers are allowed
$Data = [int] (Read-Host "Enter the count of Datastore")
$ds = "-sm-"
$Range1 = $Worksheetx1.Range("B1","B1048570")
$Search1 = $Range1.find($ds)
$r = $Search1.row
$Datastore = #()
for ($i=1; $i -le $Data; $i++) {
# Todo: Check if $r is in a valid range or not !!!
$Datastore += $Worksheetx1.Cells.Item($r, 2).Value2
$r = $r+1
}
$Datastore
I have a script with a foreach cycle. It has about a dozen functions, each collecting information from remote machines' C$ share (cutting text files, checking file version, etc.)
This is however taking some time, since each machine's data collected after one by one. (sometimes it runs with 500+ input)
Wish to put this into runspaces with parallel execution, but so far no examples worked. I am quite new to the concept.
Current script's outline
$inputfile = c:\temp\computerlist.txt
function 1
function 2
function 3, etc
foreach cycle
function 1
function 2
function 3
All results written to screen with write-host for now.
This example pings a number of server in parallel, so you easily can modify it for your demands:
Add-Type -AssemblyName System.Collections
$GH = [hashtable]::Synchronized(#{})
[System.Collections.Generic.List[PSObject]]$GH.results = #()
[System.Collections.Generic.List[string]]$GH.servers = #('server1','server2','server3');
[System.Collections.Generic.List[string]]$GH.functions = #('Check-Server');
[System.Collections.Generic.List[PSObject]]$jobs = #()
#-----------------------------------------------------------------
function Check-Server {
#-----------------------------------------------------------------
# a function which runs parallel
param(
[string]$server
)
$result = Test-Connection $server -Count 1 -Quiet
$GH.results.Add( [PSObject]#{ 'Server' = $server; 'Result' = $result } )
}
#-----------------------------------------------------------------
function Create-InitialSessionState {
#-----------------------------------------------------------------
param(
[System.Collections.Generic.List[string]]$functionNameList
)
# Setting up an initial session state object
$initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
foreach( $functionName in $functionNameList ) {
# Getting the function definition for the functions to add
$functionDefinition = Get-Content function:\$functionName
$functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $functionName, $functionDefinition
# And add it to the iss object
[void]$initialSessionState.Commands.Add($functionEntry)
}
return $initialSessionState
}
#-----------------------------------------------------------------
function Create-RunspacePool {
#-----------------------------------------------------------------
param(
[InitialSessionState]$initialSessionState
)
$runspacePool = [RunspaceFactory]::CreateRunspacePool(1, ([int]$env:NUMBER_OF_PROCESSORS + 1), $initialSessionState, $Host)
$runspacePool.ApartmentState = 'MTA'
$runspacePool.ThreadOptions = "ReuseThread"
[void]$runspacePool.Open()
return $runspacePool
}
#-----------------------------------------------------------------
function Release-Runspaces {
#-----------------------------------------------------------------
$runspaces = Get-Runspace | Where { $_.Id -gt 1 }
foreach( $runspace in $runspaces ) {
try{
[void]$runspace.Close()
[void]$runspace.Dispose()
}
catch {
}
}
}
$initialSessionState = Create-InitialSessionState -functionNameList $GH.functions
$runspacePool = Create-RunspacePool -initialSessionState $initialSessionState
foreach ($server in $GH.servers)
{
Write-Host $server
$job = [System.Management.Automation.PowerShell]::Create($initialSessionState)
$job.RunspacePool = $runspacePool
$scriptBlock = { param ( [hashtable]$GH, [string]$server ); Check-Server -server $server }
[void]$job.AddScript( $scriptBlock ).AddArgument( $GH ).AddArgument( $server )
$jobs += New-Object PSObject -Property #{
RunNum = $jobCounter++
JobObj = $job
Result = $job.BeginInvoke() }
do {
Sleep -Seconds 1
} while( $runspacePool.GetAvailableRunspaces() -lt 1 )
}
Do {
Sleep -Seconds 1
} While( $jobs.Result.IsCompleted -contains $false)
$GH.results
Release-Runspaces | Out-Null
[void]$runspacePool.Close()
[void]$runspacePool.Dispose()
This would be concurrent and run in about 10 seconds total. The computer could be localhost three times if you got it working.
invoke-command comp1,comp2,comp3 { sleep 10; 'done' }
Simple attempt at api (threads):
$a = [PowerShell]::Create().AddScript{sleep 5;'a done'}
$b = [PowerShell]::Create().AddScript{sleep 5;'b done'}
$c = [PowerShell]::Create().AddScript{sleep 5;'c done'}
$r1 = $a.BeginInvoke(); $r2 = $b.BeginInvoke() $r3 = $c.BeginInvoke()
$a.EndInvoke($r1); $b.EndInvoke($r2); $c.EndInvoke($r3)
a done
b done
c done
I'm trying to run PowerShell jobs in parallel (500) but for some reason it seems most jobs run one after the other, any idea what I'm doing wrong?
$jobs = {
Param(
[string]$thread_number_VDM,
[string]$thread_number_FS
)
for ($i=1; $i -le 60; $i++) {
#blablabla check something for 60 seconds
# sleep 1
}
}
###### main #####
for ($x=1; $x -le 50; $x++) {
for ($z=1; $z -le 10; $z++) {
Start-Job -ScriptBlock $jobs -ArgumentList ($x, $z)
}
}
I have a CheckedListBox in Powershell. When i select some checkbox the text result is empty.
When i select a second checkbox the first checkbox result text is displayed.
I use the following code for the CheckedListBox:
# Code
$ListView = New-Object System.Windows.Forms.CheckedListBox
$ListView.Location = New-Object System.Drawing.Size(10,40)
$ListView.Size = New-Object System.Drawing.Size(533,325)
$ListView.CheckOnClick = $True
$ListView.Add_ItemCheck({
for ($i = 0; $i - ($ListView.Items.Count-1); $i++)
{
if ($ListView.GetItemChecked($i))
{
$s = $s + $ListView.Items[$i].ToString();
}
}
Write-host $s
})
GetItemChecked($i) will only return the correct result for the item check that raised the event after the event handler has run.
You can inspect the event arguments for the new value of the item:
$ListView.Add_ItemCheck({
param($sender,$e)
$s = ""
for ($i = 0; $i -le ($ListView.Items.Count-1); $i++)
{
# Check if $i is the index of the item we just (un)checked
if($e.Index -eq $i)
{
# Inspect the new checked-state value
if($e.NewValue -eq 'Checked')
{
$s += $ListView.Items[$i]
}
}
elseif ($ListView.GetItemChecked($i))
{
# Item is already checked
$s += $ListView.Items[$i]
}
}
Write-host $s
})
The abstracted code:
for($i=0;$i -le $filecount;$i++){
$name = $droper.Items.Item($i).text
$copytemp = Split-Path $name.ToString() -leaf -resolve
$pasteitem = $datepath+"\" + $copytemp
$setclipboard = [System.Windows.Clipboard]::SetFileDropList($name)
#$t= [System.IO.File]::copy(,$true)
$t = [Microsoft.VisualBasic.FileIO.FileSystem]::CopyFile($name, $pasteitem, Microsoft.VisualBasic.FileIO.UIOption]::AllDialogs)
}
This works perfectly, except that for every loop for every file it copies the dialog appears.
Any way to have this copy dialog to copy all the file in array or loop only once?
When in doubt, read the documentation. If you tell CopyFile() to show all dialogs ([Microsoft.VisualBasic.FileIO.UIOption]::AllDialogs) then it most certainly will do as it's told. Call CopyFile() without that option if you don't want the dialogs:
$t = [Microsoft.VisualBasic.FileIO.FileSystem]::CopyFile($name, $pasteitem)
or (better yet), do it the PoSh way:
for($i=0; $i -le $filecount; $i++) {
$name = $droper.Items.Item($i).text
Copy-Item $name "$datepath\"
}
You can add Write-Progress to the mix if you want the overall progress displayed:
for($i=0; $i -le $filecount; $i++) {
$name = $droper.Items.Item($i).text
Write-Progress -Activity 'Copying ...' -Percent ($i*100/$filecount) -Current $name
Copy-Item $name "$datepath\"
}
If you need a graphical overall progress bar you'll probably need to build it yourself. Jeffrey Hicks published an example here.
Add-Type -Assembly System.Windows.Forms
$form = New-Object Windows.Forms.Form
$form.Text = 'Copying ...'
$form.Height = 100
$form.Width = 400
$form.StartPosition = [Windows.Forms.FormStartPosition]::CenterScreen
$progress = New-Object Windows.Forms.ProgressBar
$progress.Name = 'progressBar1'
$progress.Left = 5
$progress.Top = 40
$progress.Value = 0
$progress.Style = 'Continuous'
$drawingSize = New-Object Drawing.Size
$drawingSize.Width = 360
$drawingSize.Height = 20
$progress.Size = $drawingSize
$form.Controls.Add($progress)
$form.Show()
[void]$form.Focus()
for($i=0; $i -le $filecount; $i++) {
$name = $droper.Items.Item($i).text
Copy-Item $name "$datepath\"
$progress.Value = [int]($i*100/$filecount)
$form.Refresh()
}
$form.Close()