-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Enable 'partial' AOT of the CLI #48790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@marcpopMSFT this is what I was mentioning earlier @nagilson This could be used to AOT the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a smart strategy and respect that you even have a proposed PR with changes to make this happen. 👏
I would consult @MiYanni before merging as this may conflict with some of his restructuring work.
One thing I don't understand yet at a quicker glance, is how the muxer would be determined and how it'd know which dll to run (the aot dll seems to pick based on its list of known sdkArgs, but that does not replace the actual muxer, right?) Would there be a shared data source that is versioned with the muxer, or the muxer would have an option to always try the aot dll first? Otherwise, there is a big issue where the muxer would need to know that .NET SDK 10.0.0 Preview Blah can only aot dotnet --version but Preview Blah+1 can aot dotnet --info etc.
One nit: It might be possible to use IntPtr
instead of the byte**? I might be wrong. I am familiar with c++ but not native programming using c#.
Right -- the muxer (dotnet.exe) would still exist and we (the host team) would produce it. It would still be the entry point for all user actions. However, we would change the muxer to opportunistically load The change here is that we would alter the muxer to first look for a
Yup you could. I like to use pointers when possible because C# at least forces you to explicitly cast between them if they're not the same. IntPtr doesn't have any static type checking so you're on your own. |
Do we have any sense for what this will end up saving us? Our most common commands are restore, build, and test which I imagine all take a significant amount of time. Would we expect to eventually AOT those upstream dependencies as well? Would this mainly just save on overhead and speed up commands that should be faster like --info? Do we know the rough size of that savings? As for drawbacks, what's the impact to the SDK size as well as sdk maintenance? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not plan on making something named dotnet-shared
right now. To give some high-level context, we are planning on creating a distributable CLI package. This package would add a layer on top of System.CommandLine that has features specific to the dotnet CLI. This will require a bunch of refactoring for the CLI in this repo. This is to help encourage consistency for anything wanting to integrate with the dotnet CLI.
That endeavor will take time and has the potential to increase some performance aspects of the CLI as the code gets cleaned up and consolidated. Adding this additional shim-esque project and renaming everything else will make it more complicated to do this kind of refactoring.
If the main goal here is performance, my approach to performance would be to:
- Measure the performance of high-usage commands (based on telemetry)
- Determine the "long tails" of those commands and see what can be streamlined to reduce performance overhead for those aspects
- If startup perf is the only focus, then we'd also need to look at the runtime muxer to see if something can be made more streamlined there
My concerns about the approach proposed in this PR:
- Traditionally, the CLI has been the "wild west" and many parts of the code just sit for many years with no significant changes or focus. If you make this proposal about "converting command piecemeal", it has a likelihood that the conversion process would be started, but never completed simply because priorities change.
- I question the actual perf impact versus the engineering overhead incurred here. I fully believe that actual aspects of the running commands themselves can be improved to give greater performance gain.
- Lastly, I still plan on changing the
dotnet
project toMicrosoft.DotNet.Cli
at some point, without changing the output DLL name. My initial PR to do this was rejected but as I create the externally shared library, there will likely be a migration to the project space as that name. I haven't determined the name for the external library yet, though.
Thanks for reviewing!
If there are specific operational concerns, it makes sense to work around them. I would leave it up to you to decide precisely when to merge this and how to structure the dependencies. As long as the native entry point is well known, everything downstream only matters to the CLI.
This actually sounds like the right solution to me -- if it's never finished, that's because some pieces are too hard to move over or not worth it to move over. That sounds fine.
These are good suggestions but they're a form of "top-down" optimization. Top-down optimization looks at hot spots and is primarily useful for addressing the general goal of "improving performance." But the goal here isn't general performance optimization, it's fitting the SDK into a specific "performance profile." For that we need to do "bottoms-up" optimization, where we define the goal, list all the work we need to do (and what we don't need to do), and allocate a budget for each piece of work. The profile we're trying to fit into is defined by two things:
For (1), I looked at other language stacks for building hello-world: #33741. The results were that every competitor was < 1s, and some were <500ms. Scripting languages would of course be even faster. For (2), we're interested in the time limits associated with different classes of user experience. Per https://www.nngroup.com/articles/response-times-3-important-limits/, these are 100ms for instantaneous response, 1s for uninterrupted flow of thought, and 10s for keeping the user engaged. For most operations we definitely want to stay under 1s. There are certain operations (build, publish) where that's not realistic because they do significant compilation that scales with project size. In those cases we should focus on providing a progress indicator instead. But for all other operations: So, the conclusion of both of those things seems to point in the same direction -- we should aim for a budget of significantly < 100ms for the CLI alone, probably closer to 50ms. This simply isn't going to be possible without AOT. The CLR load time for "hello world" is 30ms on a fast machine in the best case. The CLI is more code, more JITing, and more work. Right now it's 110ms in the best case on my fastest machines, just doing |
We will be discussing this during our Wednesday design meeting tomorrow, I believe. |
Results of discussion: From the list, some that pop-out to us as interesting on dotnet run and dotnet new as ones where the non-AOT could be a larger impact to the overall command. The hello world run for example drops from 1800ms for a full run to 700ms for a --no-build run to 70ms for a direct call to the .exe file. For new, we do an implicit restore so we'd have to discuss what an AOT CLI command for restore would do. Main issues that need to be resolved for some of the initial scenarios:
Other open questions:
|
This PR demonstrates how the SDK could incrementally move pieces to Native AOT, while still keeping support for things that are incompatible.
The basic idea is as follows:
Dotnet.Shared.dll
, except for MainMain
in the existingdotnet.dll
dotnet-aot.csproj
that compiles with Native AOT to a nativedotnet_aot.dll
. This DLL implements a custom .NET host by loadinghostfxr
, starting the runtime, and runningdotnet.dll
.All the existing code continues to be accessible through the
dotnet.dll
Main
. However,dotnet_aot.dll
provides a new native entry point. Currently the muxer (dotnet.exe) calls intodotnet.dll
. However, the muxer could be modified to opportunistically loaddotnet_aot.dll
instead.dotnet_aot.dll
now has the opportunity to examine the aruments and execute any code before runningdotnet.dll
.With this implementation, the SDK could gradually and opportunistically move code to Native AOT, while still keeping incompatible pieces functioning like before.