Is dot-sourcing slower than just reading file content?

Setting up science

First, some scripts to help us test this. This generates 2000 script files, each with a single small function:

1..2000 | % { "Function Test$_(`$someArg) { Return `$someArg * $_ }" > "test$_.ps1" }

That ought to be enough to make the normal startup overhead not matter too much. You can add more if you like. This loads them all using dot-sourcing:

dir test*.ps1 | % {. $_.FullName}

This loads them all by reading their contents first:

dir test*.ps1 | % {iex (gc $_.FullName -Raw)}

Now we need to do some serious inspection of how PowerShell works. I like JetBrains dotPeek for a decompiler. If you've ever tried to embed PowerShell in a .NET application, you'll find that the assembly that includes most of the relevant stuff is System.Management.Automation. Decompile that one into a project and a PDB.

To see where all this mysterious time is being spent, we'll use a profiler. I like the one built into Visual Studio. It's very easy to use. Add the folder containing the PDB to the symbol locations. Now, we can do a profiling run of a PowerShell instance that just runs one of the test scripts. (Set the command-line parameters to use -File with the full path of the first script to try. Set the startup location to the folder containing all the tiny scripts.) Once that one is done, open the Properties on the powershell.exe entry under Targets and change the arguments to use the other script. Then right-click the topmost item in Performance Explorer and choose Start Profiling. The profiler runs again using the other script. Now we can compare. Make sure you click "Show All Code" if given the option; for me, that shows up in a Notifications area in the Summary view of the Sample Profiling Report.

The results come in

On my machine, the Get-Content version took 9 seconds to go through the 2000 script files. The important functions on the "Hot Path" were:

Microsoft.PowerShell.Commands.GetContentCommand.ProcessRecord
Microsoft.PowerShell.Commands.InvokeExpressionCommand.ProcessRecord

This makes a lot of sense: we have to wait for Get-Content to read the content from disk, and we have to wait for Invoke-Expression to make use of those contents.

On the dot-source version, my machine spent a little over 15 seconds to work through those files. This time, the functions on the Hot Path were native methods:

WinVerifyTrust
CodeAuthzFullyQualifyFilename

The second one there appears to be undocumented, but WinVerifyTrust "performs a trust verification action on a specified object." That's about as vague as you can get, but in other words, that function verifies the authenticity of a given resource using a given provider. Note that I haven't enabled any fancy security stuff for PowerShell, and my script execution policy is Unrestricted.

What that means

In short, you're waiting for each file to be verified in some way, probably checked for a signature, even though that's not necessary when you don't restrict the scripts that are allowed to run. When you gc and then iex the contents, it's like you had typed the functions at the console, so there's no resource to verify.