Powershell: pwsh is 2 times slower than powershell to load

Created on 21 Mar 2018  路  39Comments  路  Source: PowerShell/PowerShell

Powershell 6 is slow to start up compared to powershell

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

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      17115  1

vs

Major  Minor  Patch  PreReleas BuildLabel
                     eLabel
-----  -----  -----  --------- ----------
6      0      2


mus@mus-laptop ~
> time {powershell -NoProfile -c exit}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 233
Ticks             : 2337327
TotalDays         : 2.70523958333333E-06
TotalHours        : 6.492575E-05
TotalMinutes      : 0.003895545
TotalSeconds      : 0.2337327
TotalMilliseconds : 233.7327



mus@mus-laptop ~
> time {pwsh -NoProfile -c exit}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 540
Ticks             : 5400421
TotalDays         : 6.25048726851852E-06
TotalHours        : 0.000150011694444444
TotalMinutes      : 0.00900070166666667
TotalSeconds      : 0.5400421
TotalMilliseconds : 540.0421





> time {cmd /c exit}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : **42**
Ticks             : 426119
TotalDays         : 4.93193287037037E-07
TotalHours        : 1.18366388888889E-05
TotalMinutes      : 0.000710198333333333
TotalSeconds      : 0.0426119
TotalMilliseconds : 42.6119



Winver: 17115.1

WG-Engine-Performance

Most helpful comment

I feel like this conversation got unecessarily defensive.

We all know that some of the tools mentioned are like comparing a sedan to a coupe. So what? Do you want to start comparing performance of actual scripts in these tools doing actual tasks? _None of that actually matters, because the simple topic in the original post is correct:_

The startup time of PowerShell 6.0.2 is around _double_ that of PowerShell 5.1

The startup time of other scripting languages just serves to put the speed in perspective: even though it's measured in milliseconds, even the "faster" speed is slow.

All 39 comments

Looks like assemblies aren't being crossgen'd so startup time is affected by JIT

PS C:\Program Files\PowerShell\6.0.1> [System.Reflection.AssemblyName]::GetAssemblyName("${pwd}\System.Management.Automation.dll") | fl *


Name                  : System.Management.Automation
Version               : 6.0.1.0
CultureInfo           :
CultureName           :
CodeBase              :
EscapedCodeBase       :
ProcessorArchitecture : MSIL
ContentType           : Default
Flags                 : PublicKey
HashAlgorithm         : SHA1
VersionCompatibility  : SameMachine
KeyPair               :
FullName              : System.Management.Automation, Version=6.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35

Looks like assemblies are being crossgen'd. However, getting many messages from crossgen.exe that certain things aren't supported, which means those methods stay as MSIL (which probably explains why the assembly says it's MSIL). This also means that the MSIL needs to be JIT on startup. I think this is what's impacting the startup performance. We'll need to continue this investigation to see if anything we can do here.

Honestly even powershell 5 is very slow (6 times slower) than CMD. It would be good to have powershell 6 within 2~3 times the speed of cmd.

For example on my computer python is only 2 times slower than CMD to start...

It would be great to have better startup time since custom profiles add on top of that. But honestly, PowerShell easily makes up those seconds when it comes to getting stuff done (not only in terms of time to run but also the time to write the code/command) 馃槃

It would be great to have better startup time since custom profiles add on top of that. But honestly, PowerShell easily makes up those seconds when it comes to getting stuff done (not only in terms of time to run but also the time to write the code/command) 馃槃

Honestly, this is not a very good argument. Same thing could be said about windows start up times or python start up times. First time to boot/startup is pretty important.

On the other hand, cmd.exe is a native exe that does next to nothing on startup. pwsh.exe is a managed exe that sets up a complex execution environment.

We should of course strive to improve the startup performance, but comparing to completely different environments with very diffrent capabilities is not really relevant.

On the other hand, cmd.exe is a native exe that does next to nothing on startup. pwsh.exe is a managed exe that sets up a complex execution environment.

We should of course strive to improve the startup performance, but comparing to completely different environments with very diffrent capabilities is not really relevant.

Agreed, which is why I included Powershell 6 and Python 3 performance relative to CMD. It seems that powershell should be able to be within 2-3 times the startup speed of CMD, putting it in line with other interactive languages.

Agreed, which is why I included Powershell 6 and Python 3 performance relative to CMD. It seems that powershell should be able to be within 2-3 times the startup speed of CMD, putting it in line with other interactive languages.

Being 'interactive' is just one trait of many. The environments are _very_ different.
PowerShell has an adaptable type system that abstracts many different underlying type systems. It has an extensible formatting system that renders objects to text. It has an object based pipeline. It has an adaptable prompt, tab completion etc.

All of these takes time to initialize, especially the extensible types and formatting systems.

As Steve said, there is an issue with the assemblies not being crossgen-ed as they should, so we spend quite a lot of extra time jitting IL. That must be fixed.

But even with that fixed, there are differences in scope and the dependencies on the CLR/DLR that will make powershell slower to start than cmd and python.

I would venture to guess that if python were to have a more expressive REPL and a deep framework available and ready to use on the first line, its load times would be on-par with pwsh. I have seen plenty of python scripts with long init times due to dependency loading for more advanced uses. Python starts fast by default because it loads with less available by default. Comparing the start times between PowerShell and other shells and scripting languages is a bit hard to do unless they are all closer in default feature parity. none of the languages I have experience with come with as mush on-by-default as PowerShell does (for better or worse).

One idea is to do the jitting the first time the function is called (which will make the first time to actually call the function slow, but subsequent calls fast). In addition to this, a set of frequently utilized functions can be precompiled/jitted functions on startup.

@musm, the whole product is crossgen:ed. What @SteveL-MSFT said in the beginning is that something in this process is failing which makes process startup slower.

I don't think there's much we can do in 6.1.0. Will follow-up with dotnetcore team.

I feel like this conversation got unecessarily defensive.

We all know that some of the tools mentioned are like comparing a sedan to a coupe. So what? Do you want to start comparing performance of actual scripts in these tools doing actual tasks? _None of that actually matters, because the simple topic in the original post is correct:_

The startup time of PowerShell 6.0.2 is around _double_ that of PowerShell 5.1

The startup time of other scripting languages just serves to put the speed in perspective: even though it's measured in milliseconds, even the "faster" speed is slow.

I would close as well because the regression that happened once is fixed and 6.1 launches now like a rocket.

I'm not seeing any sort of improvement in launch time on 6.1 preview-3. It is disappointing that 6.1 is slower to launch than both 5.1 and 6.0.2 given all the perf improvement talk on .NET Core 2.1:
image

Powershell 5.1 vs Powershell 6.2:

2019-06-18-11-05-45

OS Name: Microsoft Windows Server 2016 Standard
OS Version: 10.0.14393 N/A Build 14393

Looks like we still have an issue with 7-preview.1. .NET team is already working on improvements to startup, we should continue to check this with new previews.

PS C:\Users\slee> $total = 0; 1..100 | % { $total += (Measure-Command { powershell -nop -nologo -c 1 }).TotalMilliseconds };$total/100
190.219938
PS C:\Users\slee> $total = 0; 1..100 | % { $total += (Measure-Command { pwsh-preview -nop -nologo -c 1 }).TotalMilliseconds };$total/100
452.461919
PS C:\Users\slee> $total = 0; 1..100 | % { $total += (Measure-Command { pwsh -nop -nologo -c 1 }).TotalMilliseconds };$total/100
346.560211
> $total = 0; 1..100 | % { $total += (Measure-Command { pwsh-preview -nop -nologo -c 1 }).TotalMilliseconds };$total/100;
394.33809

still quite slow on 7.0.0-preview.2, as well

For Steve's one-liners, I have (on Windows):

| PS version | Time (ms) |
| ----------- | ----------- |
| 5.1 | 404 |
| 6.2.2 | 444 |
| 7.0.0-preview.2 | 546 |
| 7.0.0-dailypreview3-30639 | 494 |

Obviously we added some commits at 7.0 Preview3 time which slow down startup compared with 6.2.
I prepared a fix which remove ~10ms in the Steve's test.
And I am thinking about still another fix which I hope save some ms.

I have often decided to use bash (via git for Windows, not WSL) purely because it starts up "nearly instantly" enough. Powershell (any version I've tried, including Core / 6) is slow enough on the machine I have to use at work.

Slow startup time is also a big criticism I've heard from more Linuxy people I've talked to. I've convinced some of them that structured objects are better than text, but the startup time and general sluggishness compared to other shells puts them off.

There's still work to be done to improve startup perf. Using an updated version of my test:

PSVersion       Time (ms)
---------       ---------
5.1.18362.10000    169.64
6.2.1              311.96
7.0.0-preview.3    399.98

Note that without major changes, it's unlikely we will be as fast as 5.1 because it uses ngen which means .NET Framework does native compilation for your specific system on the first cold start of powershell.exe. .NET Core doesn't have ngen so we rely on crossgen to pre-compile to a target architecture, but crossgen isn't as complete as ngen in generating native code because it's precompiled rather than at runtime.

Whilst further optimising the startup would be nice, we should not forget that this startup performance is one thing, but PowerShell's general performance also plays an important part where potentially more time can be shaved off. My profile takes nearly a second due to some modules like posh-git taking a long time to load. Due to general improvements, 7.0 is still faster than 6.2 to use for me simply because my $Profile loads faster. Out of curiosity: would it be e.g. possible to import a module in a non-blocking way as jobs don't work in this scenario? Using Invoke-Pester the first time takes over a second as well the first time due to the module needing to be imported (the module is currently not optimised with the module builder pattern to squeeze everything into a monolithic file at build time) and I see this part of the negative, laggy experience that PowerShell has in some scenarios (don't even get me started on tab completion, which sometimes takes seconds and even cancellation of a it takes ages....)

Having used PowerShell 2/3 extensively back on Windows, then having used bash/sh extensively on Linux, the startup time difference is absolutely staggering.

Where it really makes it painful for someone on Linux, is when running executable scripts using a shebang. Typically with bash you write a lot of scripts with a shebang, e.g #!/usr/bin/env bash

you also also might run numerous scripts in a row that call other scripts that also use the shebang

e.g.

./foo

#!/usr/bin/env bash

./bar

./bar

#!/usr/bin/env bash

echo "Hello from bar!"

With bash/sh, this isn't a problem, you don't really notice any speed issues opening subshells / new processes like this.

You also might be calling numerous executable scripts in a row from the interactive shell.

The reason this matters, is that with pwsh, using the shebang (#!/usr/bin/env pwsh) causes each script ran to run as slow as that "first startup".

That means taking the example above, calling foo would take 2x the time it takes to just run pwsh.

It also means that running multiple powershell scripts (that use shebangs) in an interactive shell (either from say bash, or even already from powershell), is extremely slow.

Compare this to when you first run pwsh, and then from inside that shell, run the exact same scripts but as .ps1 files rather than relying on the shebang. It runs basically instantaneously (within the existing pwsh process).

To me, this is the biggest issue with the slow pwsh startup. It's not that it's slow on startup the first time that really matters, it's the fact that using shebangs means it makes every single invocation that slow or slower.

Switching from using shebangs to making everyone type pwsh -File foo.ps1 or whatever, is not a smooth transition. Speaking from experience, I never call bash scripts by typing bash foo.bash, I make them executable and call them directly.

My profile takes nearly a second due to some modules like posh-git taking a long time to load.

don't even get me started on tab completion, which sometimes takes seconds and even cancellation of a it takes ages....

It seems a root of the issue is discussed in #10309 - Safer API slow down the import module code path notably (there is extra double call the Safer API!).

I still am finding pwsh is twice as slow on the latest version compared to powershell

Quoted my comment on a related issue https://github.com/PowerShell/PowerShell/issues/7402#issuecomment-576943388

From 5.1 to 6.0/6.1/6.2, the worse startup time was mainly because of the use of crossgen vs. ngen. ngen, which is used by powershell.exe, generates more efficient code as it's targeting the particular machine the code runs on; while crossgen has to target a wide range of machines and thus generates code that's less efficient but more resilient.
In 6.2 time frame, #8341 improved the startup time by about 25% comparing with 6.1.

From 6.2 to 7.0, the startup time regressed mainly because .NET Core started to use the Ready-to-Run format for System.Private.CoreLib.dll and maybe a few other core libraries. They were ngen'ed previously.

Can this be compiled with CoreRT?

Quote from the CoreRT README.md:

Unsupported features: Dynamic loading (e.g. Assembly.LoadFile), dynamic code generation (e.g. System.Reflection.Emit), Windows-specific interop (e.g. COM, WinRT)

PowerShell depends on the Dynamic Language Runtime (DLR) which heavily depends on dynamic code generation. Also, dynamic loading is a fundamental requirement for PowerShell.

Powershell 7 times:

image

pwsh 2.2x slower than powershell

I was going to upgrade because 5.1 has a font issue, but version 7 is half the speed as others have said.

@nu8 Just to be clear, it's only the startup suffering the perf degradation. The steady runtime performance is much better in PowerShell 7. So unless you are heavily relying on pwsh -c or pwsh -file in your daily workflow, it should be good for you to upgrade to PowerShell 7.

That's precisely the issue and the reason I opened this @nu8.

I suspect we are not the only ones preaching about this.

I right click and open PowerShell in the current directory many times a day. It's painfully slow, especially when I compare it to the near instant times of, say, launching WSL.

The testing here is all without loading a profile (-NoProfile) and looking at startup times of a shell of almost 1 second. I would consider even 0.1 second as being a tad on the slow side.

|Version|Startup Time|
|-|-|
|5|0.3s|
|6.2.3|4.5s|
|7.0.2|0.6s|

Mind you that with plugins enabled startup will suffer even more. I have two plugins enabled: PoshGit and Z-AutoJump. My startup time with these two plugins is:

|Version|Startup Time|
|-|-|
|5|2.0s|
|6.2.3|4.8s|
|7.0.2|2.2s|

Compare that with startup times on macOS (iTerm):
|Shell|Startup Time|
|-|-|
|zsh 5.7.1 (no plugins)|0.00s|
|zsh 5.7.1 (with lots of plugins)|0.14s|
|pwsh 7.0.0 (no plugins)|0.43s|

@Bouke totally agree. Note, posh-git is notoriously bad for adding at least 500ms to the start time.

And yes even 1 second is a ridiculous start time for an interactive shell. Start times need to get under 100ms for the best UX.

As someone, who has pwsh as his default shell under linux, this greatly affects me, so I would much appreciate any possible improvement! Not only is launching a terminal window noticeably slower, but I just cannot have quick powershell scripts run on my hotkeys. (Which I have quite a few of that I rely on, since I'm using sway as a window manger.)

I need to go back to bash or perl for these purposes, which are much more infuriating to write stuff in, than powershell, which is in my opinion a much better system overall, that's why I'm sticking to it, even though there are quite a few small inconveniences. (Such as for example the '<' file redirection operator missing, which is expected by some programs, such as vim, but luckily it can be fixed by telling it to use pipes instead.)

Anyway, some updates that make pwsh -c viable for more or less instantaneous small things would be very welcome!
Thanks for all the work of the development team, my life is already much better by having a shell with proper syntax and programmability (e.g. the | % { } foreach and such things are awesome! That same thing would be so much more complicated in bash...) instead of the _"everything's just text, use cut, sed, awk, grep, ..."_ mess that all the POSIX compatible shells are!

@Isti115 Fundamental problem is that managed runtime initialization is significantly slower than native code. .Net team made many improvements but managed will never be as fast as native at startup.
In the repo performance improvements was also made. I do not think we can make more in the milestone.

Cheat: you can create and run simple C# infinite service with one function - load all PowerShell dlls.
(As result all dlls will be already in memory before you run PowerShell)
(For the service you can use PowerShell too - run and Start-Sleep -Seconds 65535.)

Can parts of the initialization be done after the REPL is started?

  1. Initialize necessary code for the REPL
  2. Initialize everything else afterwards.

I think REPL is more for C# scripts.
For PowerShell there is a hosting idea from Jason but it is not easy to implement I guess.

Hmm, reading all the suggestions I came up with the idea of a simple PowerShell script running continuously in the background reading a FIFO as it's input and executing whatever commands get piped in there. I know that this is a very messy solution, because variables and contexts could get mixed up, but if I am careful enough when writing the commands and ensure that they have no side effects (I mean, in terms of the execution context, otherwise it would make no sense to run the commands at all. :smile:) this could work.

Was this page helpful?
0 / 5 - 0 ratings