runtime icon indicating copy to clipboard operation
runtime copied to clipboard

`PublishReadyToRun=true` significantly regresses startup time when used with `EnableCompressionInSingleFile=true`

Open JakeYallop opened this issue 1 year ago • 1 comments

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.

JakeYallop avatar May 03 '24 21:05 JakeYallop

/cc @fanyang-mono @ivdiazsa

steveisok avatar May 06 '24 15:05 steveisok

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

ivdiazsa avatar Jun 25 '24 21:06 ivdiazsa

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

.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.

JakeYallop avatar Jun 26 '24 17:06 JakeYallop

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.

ivdiazsa avatar Jun 26 '24 18:06 ivdiazsa

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.

JakeYallop avatar Jun 26 '24 19:06 JakeYallop

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?

ivdiazsa avatar Jul 31 '24 21:07 ivdiazsa

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?

JakeYallop avatar Jul 31 '24 21:07 JakeYallop

The regression is expected, we just have to document it further.

ivdiazsa avatar Jul 31 '24 23:07 ivdiazsa