sdk: dotnet run of hello world takes way too long

Reported by @migueldeicaza

Steps to reproduce

  1. dotnet new console
  2. dotnet build
  3. dotnet run

Expected behavior

dotnet run executes fast, nearly the same as calling dotnet <pathToDll>

Actual behavior

dotnet run is slow, about 3 seconds on my machine. It’s consistently this slow, even when it doesn’t need to rebuild.

It appears that run is calling msbuild twice before actually running the app: image

Can’t we have a fast-path here that bypasses MSBuild? It seems like we’ve optimized for incremental build via the run command which I’m not sure is really the primary workflow for run (but I may be wrong).

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 30
  • Comments: 60 (28 by maintainers)

Most upvoted comments

I believe dotnet run --no-build will help here. Maybe it should be the default.

We’re investigating CLI performance for the commands frequently used during development as part of .NET 6 and we definitely expect to get improvements to this very common scenario of running a simple app via dotnet run.

As it stands today, using a nightly build of .NET 6.0-preview.3, a dotnet run on a “Hello, World!” console app takes about 1.3-1.4 seconds on my machine, after a warm-up run or two. Running the app directly takes ~60ms, so the dotnet run overhead is practically the entire time, which is mostly spent in the the execution of the restore and build itself.

Progress on this work can be followed at https://github.com/dotnet/msbuild/issues/5876

It is quite slow when I just run the Console.WriteLine("Hello World!"); on Macos.

If you want to run your application after it’s already been compiled, do this: dotnet bin\debug\netcoreapp2.0\ncc.dll

This is much quicker than using dotnet run, with or without --no-build, because dotnet doesn’t need to spending time figuring out which file contains the startup object.

I think the interesting thing here, is that if MSBuild was a closed system, I’m pretty damn confident we could make this blazingly fast. It’s not. By design, it’s an open extensible system and any package, SDK, project can and do supplement the build process. We have to be able to identify performance issues and fix each individual step in the process and do it in a way that makes sure you projects still build in the same ways. It’s challenging but its really rewarding to see the 10s of devs involved in this pipeline to all rally around a single goal to make builds as fast as possible.

1.8s: I’d expect that to be closer to 0.344s

@perlun That’s pretty much how the underlying build framework (MSBuild) works.

“Incremental Build” refers to additional builds started after an initial build. During this “incremental build”, steps like compiling C# code can be skipped if the output is “up to date” (when outputs of a step are not older than the inputs to the step).

The recent efforts of the team included both “classic perf work” on existing code (e.g. a community PR made MSbuild’s directory scanning much faster) as well as reducing duplicated work in incremental builds - loading cached outputs if the inputs didn’t change. This also makes incremental builds much faster even if C# code needs to be recompiled, but maybe your referenced NuGet packages and other projects didn’t change and MSBuild doesn’t need to resolve potential conflicts of same assemblies in different versions again, or search for additional dependencies it found in the first build.

The two times above are non-comparable. They are not meant to be equivalent.

dotnet <path_to_dll> is the expected way to run FDD apps and does not require the SDK. It does very little.

Meanwhile, dotnet run is a developer tool equivalent to F5 in VS or some other IDE. It does a bunch of things, like restore and build your project and then, it evaluates it to find the necessary commands to run your app and then finally, using this information, kicks off that process.

While it could, potentially be faster, it will never have the same perf as dotnet <path_to_dll> and that is very much expected.

Especially when whole windows start in ~20 seconds, and cold run of winword taking 3 seconds. That 4seconds to start the empty app is… a little bit too much… Mostly looks like it dumping the whole HDD to the cloud…

Find myself in the same boat here. Used C# with VS back in the 2006 era or so and loved the rapid development experience coupled with the IDE. I suspect VS was background compiling files as I changed them.

Now I find myself enjoying C# + dotnet while using MonoGame, but the dev experience is definitely hindered by the slow compile + launch even on very recent hardware.

Meanwhile, dotnet run is a developer tool equivalent to F5 in VS or some other IDE. It does a bunch of things, like restore and build your project and then, it evaluates it to find the necessary commands to run your app and then finally, using this information, kicks off that process.

Yeap, totally understood that there’s more going on, but you hit the nail on the head with regards to “similar to F5”… hitting F5 I’m sub 1 second and that’s including an attached debugger, so 4-5 seconds without the debugger makes for a frustrating dev experience (incidentally, no build and no restore options didn’t help).

Anyway, just adding weight as it’s putting me off that particular dev experience.

Can you confirm you are doing DotNet code.dll and not DotNet run code.dll and still seeing slow startup? If so that’s a different issue as I mentioned, and you should open an issue in the corefx repo.

@ericstj , dotnet code.dll is fast. but dotnet run code.dll is slow. I think it is easy to reproduce by yourself.

# macOS High Sierra Version 10.13.6

dotnet --version
2.1.402

@wenbinke and @argentini can you clarify if you are seeing slowness just in dotnet run and not dotnet <app.dll>? That’s what this issue is about. That slowness is related the cost of an incremental build before running the app.

If you are seeing slow execution when directly starting the app.dll then please file an issue on http://github.com/dotnet/corefx with full repro steps and we can investigate.

Hi @ericstj ,

I created the console project with code.cs. then run dotnet run code.cs. It takes a few seconds to finish. I expect it could be done in 50ms.

code.cs

using System;

namespace Hello
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

code.csproj

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
</Project>

To give a new user’s perspective: I just downloaded .net core today on my arch machine. Got here for the same reason as OP - dotnet run is unexpectedly slow. --no-build has an acceptable time though it’s still not instant which is what I expect on subsequent runs. I think it’s worth noting the first time I ran the command I wasn’t surprised because I suspected it was due to building the executable. Subsequent runs however did surprise me.

Edit: I don’t mean to infer my use-case should be supported or that my expectation speaks for everyone.

Worth pointing out there’s already dotnet run --no-build too as a quick way to just run the last compilation. Perhaps a short alias could be introduced for that if folks feel that scenario is common enough to warrant it, e.g. dotnet run -nb

Original poster here. Just sharing my opinion in why I originally opened this.

Dotnet run does a full incremental build and I didn’t expect that. I wanted something faster, if even just a hueristic like “has any build been done” rather than a full incremental.

I expect a full incremental build to take time, and I think the time it takes is reasonable.

I also think the time for a direct run of the output is reasonable.

AOT is great, and did exist before .NETCore did (UWP: .NETNative) but that’s not really going to have an impact here. The bottle neck is not JIT it’s IO for the incremental build.

If folks don’t feel like changing dotnet run to use a lighter weight hueristic is something we want to do then I suggest closing this issue and opening other issues more focused on specific perf issues they are facing.

Just started learning C#, i’m on a low end machine atm, and i´m already here. I´m very surprised that dotnet run is that slow. Since most of other programming languagues that i work with, that are compile to binary have way faster ‘hello world’ compilation times.

One thing that i noticed is that build and run the exe is faster than just the run command, which is odd: ( Not the very first build, runned multiple times make sure its cached )

timecmd dotnet run 00:00:03.38

timecmd dotnet build && bin\Debug\netcoreapp3.1\cs.exe 00:00:02.32 ( and off course, first time builds are way slower )

I´m very confused.

Every refresh of the hello world 3.0-preview7 with angular through UseProxyToSpaDevelopmentServer takes at least 4.5 seconds for me: image

As a heads up, more improvements have been made in Preview 2 - https://blogs.msdn.microsoft.com/dotnet/2018/04/11/announcing-net-core-2-1-preview-2/.

I’ll let the graph speak for itself:

image

Just started learning C#, i’m on a low end machine atm, and i´m already here. I´m very surprised that dotnet run is that slow. Since most of other programming languagues that i work with, that are compile to binary have way faster ‘hello world’ compilation times.

I’m in the exact same position as you and had the same thought! Can this issue be resolved, or is it just something we must get used to?

On a separate note, I am pushing for https://github.com/dotnet/cli/issues/2960 to get implemented in 2.1. In general - allow the CLI to be configurable. Once that happens, a configuration that could be set would be “what default dotnet run experience do you want?”. Users who know they will always build before they call dotnet run can configure it to be --no-build. Users who don’t want to bother with ensuring their project is already built before calling run can configure it to be --ensure-built.

Then it is just a matter of picking the default/unconfigured option and letting users change it if the default isn’t what they want.

Not if user expects run to do only a run without a build… The workaround is already to run with --no-build or point the host directly at the application: we don’t need more workarounds. This is issue is about validating the assumption that the majority of users expect run to also do a build. I’ve seen enough cases where people don’t expect this to question that assumption.

Furthermore, the console spew is so minimal that the user doesn’t even realize they are getting an incremental build + run. Even fixing that could make it feel a bit more natural. As it is now it just looks like we’re incredibly slow to run helloworld.

@livarcocc do we know the cause? if no i’d like to do some performance profile for this case