Skip to content

Framework-Dependent pwsh.exe Startup Time more than 2x slower then Self-Contained on Windows x64 platform #27837

Description

Prerequisites

Steps to reproduce

Сreate a test directory and a tree of subdirectories within it:
.\config
.\release\PowerShell-7.6.4-win-x64
.\release\PowerShell-7.6.4-win-fxdependent
.\release\dotnet-runtime-10.0.10-win-x64

Download and extract the installation packages to the appropriate directories:
https://github.com/PowerShell/PowerShell/releases/download/v7.6.4/PowerShell-7.6.4-win-x64.zip (.\release\PowerShell-7.6.4-win-x64)
https://github.com/PowerShell/PowerShell/releases/download/v7.6.4/PowerShell-7.6.4-win-fxdependent.zip (.\release\PowerShell-7.6.4-win-fxdependent)
https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-10.0.10-windows-x64-binaries-zip (.\release\dotnet-runtime-10.0.10-win-x64)

Create a test script:
.\measure_startup_time.ps1

$IterationNumber = 100

# create common empty powershell.config.json

New-Item -Path "${PSScriptRoot}\config\powershell.config.json" -Force | Out-Null

#  create common arg list

$ArgumentList = @(
'-NoLogo' 
'-NonInteractive' 
'-NoProfile'
'-ExecutionPolicy', 'Bypass'
'-SettingsFile',    "${PSScriptRoot}\config\powershell.config.json"
'-Command',         '#'
)

#  create common env

$env:POWERSHELL_TELEMETRY_OPTOUT         = 1
$env:POWERSHELL_DISTRIBUTION_CHANNEL     = ''
$env:POWERSHELL_UPDATECHECK              = 'Off'
$env:POWERSHELL_DIAGNOSTICS_OPTOUT       = 1

$env:PSExecutionPolicyPreference         = ''
$env:PSModulePath                        = ''
$env:PSModuleAnalysisCachePath           = 'NUL'
$env:PSDisableModuleAnalysisCacheCleanup = 1


#  measure portable zip binary pwsh.exe (Self-Contained) :
#  https://github.com/PowerShell/PowerShell/releases/download/v7.6.4/PowerShell-7.6.4-win-x64.zip

#  remove profile

Get-Item -Path "${env:LOCALAPPDATA}\Microsoft\PowerShell\StartupProfileData-NonInteractive" -ErrorAction 'SilentlyContinue' | Remove-Item 

#  run test

$SelfContained = 1..$IterationNumber | ForEach-Object { 

  Write-Progress -Id 1 -Activity 'Self-Contained     ' -PercentComplete ( ( $_ / $IterationNumber ) * 100 )

  ( Measure-Command { & "${PSScriptRoot}\release\PowerShell-7.6.4-win-x64\pwsh.exe" $ArgumentList } ).TotalMilliseconds

} | Measure-Object -Average


#  measure portable zip binary pwsh.exe (Framework-Dependent) :
#  https://github.com/PowerShell/PowerShell/releases/download/v7.6.4/PowerShell-7.6.4-win-fxdependent.zip
#  on .NET Runtime 10.0.10 :
#  https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-10.0.10-windows-x64-binaries-zip

#  set path to dotnet runtime

$env:DOTNET_ROOT = "${PSScriptRoot}\release\dotnet-runtime-10.0.10-win-x64"

#  remove profile

Get-Item -Path "${env:LOCALAPPDATA}\Microsoft\PowerShell\StartupProfileData-NonInteractive" -ErrorAction 'SilentlyContinue' | Remove-Item  

#  run test

$FrameworkDependent = 1..$IterationNumber | ForEach-Object {

  Write-Progress -Id 2 -Activity 'Framework-Dependent' -PercentComplete ( ( $_ / $IterationNumber ) * 100 )

  ( Measure-Command { & "${PSScriptRoot}\release\PowerShell-7.6.4-win-fxdependent\pwsh.exe" $ArgumentList } ).TotalMilliseconds

} | Measure-Object -Average


#  report

Write-Host ( 'IterationNumber                                         = ' + $IterationNumber.ToString() )

Write-Host ( 'Self-Contained      Average Satrtup Time (milliseconds) = ' + $SelfContained.Average.ToString( '0' ) )

Write-Host ( 'Framework-Dependent Average Satrtup Time (milliseconds) = ' + $FrameworkDependent.Average.ToString( '0' ) )

Write-Host ( 'Diff                                                    = ' + ( ( ( $FrameworkDependent.Average - $SelfContained.Average ) / $SelfContained.Average ) ).ToString( 'P0' ) )

Run the test script (.\measure_startup_time.ps1) from the PowerShell 7.6 (LTS) command line (do not use PowerShell from the .\release\PowerShell* for this).

Expected behavior

Diff                                                    = 0 %

Actual behavior

IterationNumber                                         = 100
Self-Contained      Average Satrtup Time (milliseconds) = 190
Framework-Dependent Average Satrtup Time (milliseconds) = 418
Diff                                                    = 120 %

Error details

The reason is presumably that the ReadyToRun compilation is not used during the framework dependent release . See the discussion in #14121.

dotnet-trace data in speedscope format; it can be viewed on the web at speedscope.app

Self-Contained:
pwsh_(sc).speedscope.json

Framework-Dependent
pwsh_(fd).speedscope.json

Environment data

Name                           Value
----                           -----
PSVersion                      7.6.4
PSEdition                      Core
GitCommitId                    7.6.4
OS                             Microsoft Windows 10.0.26100
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.4
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

Trace data screenshots in Speedscope "Left Heavy" mode showing that .NET code executes 2x slowly:

Self-Contained:

Image

Framework-Dependent:

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs-TriageThe issue is new and needs to be triaged by a work group.Review - MaintainerThe PR/issue needs a review from the PowerShell repo Maintainers

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions