runtime
runtime copied to clipboard
`PublishReadyToRun=true` significantly regresses startup time when used with `EnableCompressionInSingleFile=true`
Description
Using R2R along with EnableCompressionInSingleFile=true regresses startup time compared to using just EnableCompressionInSingleFile without R2R.
Reproduction
Use a new console app with the following added:
<PropertyGroup>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishSingleFile>True</PublishSingleFile>
<PublishSelfContained>True</PublishSelfContained>
<PublishReadyToRun>true</PublishReadyToRun>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
</PropertyGroup>
Then publish with
dotnet publish -c Release
Regression?
Yes, not reproducible in .NET 6.0 or .NET 7.0.
Data
Using powershell Measure-Command:
Measure-Command {.\R2R_EnableCompressionInSingleFile.exe}
| PublishReadyToRun | EnableCompressionInSingleFile | Time |
|---|---|---|
| False | False | 21ms |
| True | False | 21ms |
| False | True | 54ms |
| True | True | 138ms |
I wouldn't expect R2R to have much of an impact for a hello world app (however, it does help it my real scenario when compression is disabled), but I would not expect it to regress quite so much when EnableCompressionInSingleFile is turned on. Additionally this issue does not reproduce on .NET 6.0 or .NET 7.0.
/cc @fanyang-mono @ivdiazsa
Hi @JakeYallop, could you provide more information about your system and dotnet installation? I tried reproducing the issue you described but to no avail. Also, I'm getting very different numbers from yours with Measure-Command. For context, I wrote a quick Hello World app, and published it with the settings you detailed above. I tried it with the latest official builds of .NET 7, .NET 8, and .NET 9 Preview 5, and all of them gave me a result of ~230-260 ms
Hi, yes, I can still reproduce this. I've tried again with .NET 6, 7, 8 and 9 (preview 4).
230-260ms seems very similar to the "first run" times I'm getting, however these are not representative as they include the initial self-extraction of the single file executable. Running the same Measure-Command again gets a representative startup time.
I've written a little script for convenience:
param(
[Parameter(Mandatory = $true)]
[string] $exePath
)
function Measure-CommandWithWarmup {
param (
$path,
$description
)
for ($i = 0; $i -lt 3; $i++) {
& $path | Out-Null
}
$times = [System.Collections.Generic.List[double]]::new()
for ($i = 0; $i -lt 3; $i++) {
$time = Measure-Command { & $path }
$times.Add($time.TotalMilliseconds)
}
$times.Sort()
Write-Host "$description $($times[1])ms"
}
dotnet publish -c Release -p:PublishReadyToRun=false -p:EnableCompressionInSingleFile=false -o none
dotnet publish -c Release -p:PublishReadyToRun=false -p:EnableCompressionInSingleFile=true -o compression
dotnet publish -c Release -p:PublishReadyToRun=true -p:EnableCompressionInSingleFile=false -o r2r
dotnet publish -c Release -p:PublishReadyToRun=true -p:EnableCompressionInSingleFile=true -o r2r-compression
Measure-CommandWithWarmup "./none/$exePath" "none"
Measure-CommandWithWarmup "./r2r/$exePath" "R2R"
Measure-CommandWithWarmup "./compression/$exePath" "EnableCompressionInSingleFile"
Measure-CommandWithWarmup "./r2r-compression/$exePath" "R2R | EnableCompressionInSingleFile"
Results
.NET 6
| PublishReadyToRun | EnableCompressionInSingleFile | Time |
|---|---|---|
| False | False | 23ms |
| True | False | 24ms |
| False | True | 76ms |
| True | True | 74ms |
.NET 7
| PublishReadyToRun | EnableCompressionInSingleFile | Time |
|---|---|---|
| False | False | 20ms |
| True | False | 21ms |
| False | True | 51ms |
| True | True | 139ms |
.NET 8
| PublishReadyToRun | EnableCompressionInSingleFile | Time |
|---|---|---|
| False | False | 19ms |
| True | False | 20ms |
| False | True | 53ms |
| True | True | 137ms |
.NET 9 (Preview 4)
| PublishReadyToRun | EnableCompressionInSingleFile | Time |
|---|---|---|
| False | False | 18ms |
| True | False | 19ms |
| False | True | 54ms |
| True | True | 140ms |
dotnet --info
dotnet --info.NET SDK:
Version: 9.0.100-preview.4.24267.66
Commit: 26d3d259e4
Workload version: 9.0.100-manifests.9dc03b27
MSBuild version: 17.11.0-preview-24225-01+bd0b1e466
Runtime Environment:
OS Name: Windows
OS Version: 10.0.22631
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\9.0.100-preview.4.24267.66\
.NET workloads installed:
[maccatalyst]
Installation Source: VS 17.11.35017.193
Manifest Version: 17.2.9088-net9-p1/9.0.100-preview.1
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.1\microsoft.net.sdk.maccatalyst\17.2.9088-net9-p1\WorkloadManifest.json
Install Type: Msi
[wasm-tools]
Installation Source: VS 17.11.35017.193
Manifest Version: 9.0.0-preview.4.24266.19/9.0.100-preview.4
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.4\microsoft.net.workload.mono.toolchain.current\9.0.0-preview.4.24266.19\WorkloadManifest.json
Install Type: Msi
[ios]
Installation Source: VS 17.11.35017.193
Manifest Version: 17.2.9088-net9-p1/9.0.100-preview.1
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.1\microsoft.net.sdk.ios\17.2.9088-net9-p1\WorkloadManifest.json
Install Type: Msi
[maui-windows]
Installation Source: VS 17.11.35017.193
Manifest Version: 9.0.0-preview.1.9973/9.0.100-preview.1
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.1\microsoft.net.sdk.maui\9.0.0-preview.1.9973\WorkloadManifest.json
Install Type: Msi
[android]
Installation Source: VS 17.11.35017.193
Manifest Version: 34.99.0-preview.1.151/9.0.100-preview.1
Manifest Path: C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.1\microsoft.net.sdk.android\34.99.0-preview.1.151\WorkloadManifest.json
Install Type: Msi
Host:
Version: 9.0.0-preview.4.24266.19
Architecture: x64
Commit: d0d6fed6c0
.NET SDKs installed:
8.0.106 [C:\Program Files\dotnet\sdk]
8.0.300 [C:\Program Files\dotnet\sdk]
9.0.100-preview.4.24267.66 [C:\Program Files\dotnet\sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 6.0.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.19 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 9.0.0-preview.4.24267.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 6.0.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.19 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 9.0.0-preview.4.24266.19 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 6.0.30 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.19 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 9.0.0-preview.4.24267.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]
Environment variables:
Not set
global.json file:
Not found
Learn more:
https://aka.ms/dotnet/info
Download .NET:
https://aka.ms/dotnet/download
Just let me know if there's anymore details you'd like.
Thanks for the detailed information. I'm still worried about the numbers though because the 230-260 I got in warm runs. The cold first run gave me ~800 ms. I will try again with the script you sent and add .NET 6 as well to my experiments.
I've just double checked - the cold runs are between 400 - 500ms for me. In the comment above I was just attempting to recall the previous numbers.
Hi @JakeYallop, we've done further investigation and here's the findings. What happened is that from .NET 6 to .NET 7, composite mode was enabled by default for ReadyToRun with single file. This means that when compression is enabled, the big composite image has to be decompressed at startup, which takes longer than the individual assemblies would, and thus causing the longer times you're seeing.
You can revert this behavior by adding <PublishReadyToRunComposite>false</PublishReadyToRunComposite> to your csproj file. I ran some tests with it and the regression was gone. Could you please try this and check if it works for you?
I can confirm that this removes the regression on .NET 7/8/9.
Is it just a case of documenting this behaviour, or should composite R2R with single file compression work without causing a startup regression?
The regression is expected, we just have to document it further.