core
core copied to clipboard
Proposed .NET 7 Breaking Changes
The following list defines breaking changes that are proposed for .NET 7.
Implemented:
- https://github.com/dotnet/runtime/pull/67022
- https://github.com/dotnet/runtime/pull/69902
- https://github.com/dotnet/sdk/pull/22314
Planned for .NET 7:
- https://github.com/dotnet/sdk/issues/23551
Unclear plan:
- https://github.com/dotnet/designs/pull/173
Moved to .NET 8:
- https://github.com/dotnet/sdk/issues/23540
- https://github.com/dotnet/sdk/issues/23545
No longer honor multi-level-lookup (MLL) for .NET 7+ apps, runtimes and SDKs
Proposal: https://github.com/dotnet/designs/blob/main/accepted/2022/disable-multi-level-lookup-by-default.md Change: https://github.com/dotnet/runtime/pull/67022 Notice: https://github.com/dotnet/docs/issues/28836
Multi-level-lookup (MLL) -- specified with DOTNET_MULTILEVEL_LOOKUP -- is relevant when an alternate location is specified to look for SDKs and/or runtimes, via DOTNET_ROOT. When enabled, MLL expands the search space for runtimes and SDKs to include the global .NET installation location in addition to the DOTNET_ROOT location. This can result in a runtime or SDK being selected from the global installation location rather than the intended private DOTNET_ROOT location, whether that is desired or not.
Motivation for break:
- MLL results in lots of confusion, both for .NET developers and even for folks working on the .NET platform.
- MLL behavior differs by OS: enabled by default on Windows, not available on any other OS.
- No known scenario where this feature is required.
Stop adding 32-bit .NET to the PATH for .NET 7+ runtimes and SDKs (on x64 machine)
Proposal: https://github.com/dotnet/sdk/issues/22030 Change: https://github.com/dotnet/runtime/pull/69902 Notice: https://github.com/dotnet/runtime/issues/70039
Note: This change was made to .NET Core 3.1, .NET 6, and .NET 7.
When you install .NET, the installer adds the install location to the PATH environment variable. This behavior is sound and enables the OS to find dotnet when you use it. We build multiple .NET for multiple architectures (x86, x64, ...). In some cases, an OS supports multiple architectures and then we need a plan for how multiple .NET locations in the PATH works. In short, it doesn't work well. Going forward, we should only ever add the OS native-architecture .NET install location to the PATH. We already started doing that on Apple M1 (Arm64) machines (we only add the Arm64 .NET to the PATH, not the x64 version). We need to repeat this pattern with Windows now, with the 32-bit .NET build.
Motivation for break:
- Adding multiple builds of .NET to the
PATHis incoherent, results in bad UX, and is difficult to explain. - We have already started down this direction with x64 on Arm64. It makes sense to make the product consistent, by repeating the pattern with x86 on x64 and x86 on Arm64.
dotnet build/publish uses the implicit SDK RID for RID-specific apps by default
Proposal: https://github.com/dotnet/sdk/issues/23539 Change: https://github.com/dotnet/sdk/pull/22314 Notice: not actually breaking
Today, you must specify a RID when you specify "--self-contained". That's not a useful requirement, particularly if you want to app to be able to run in a given environment (like CI) and don't know what that is ahead of time. Instead, the implicit SDK RID should be used in any scenario where a RID is needed but one isn't provided. The most obvious example of that is to produce a self-contained app.
FYI: This is arguably not a breaking change.
Motivation for break:
- The CLI should have good defaults where correct behavior can be provided in absence of the user provided a specific value.
- RID targeting is confusing. We should make it simpler.
dotnet publish/pack produce release assets by default
Proposal: https://github.com/dotnet/sdk/issues/23551
Our basic guidance to developers since .NET Core 1.0 has been "use build for development and publish for prod". Given that, it would make a lot more sense if publish defaulted to a release build. Debug builds run observably slow (sometimes you can see this with just your eyes; no stopwatch required). It is near certain that plenty of .NET apps are deployed as debug due to the current defaults. The CLI would be much better if it offered more differentiated options. There should probably be a more broad re-assessment of build and publish but I'm not digging into that here. I'm proposing a much more simpler change (that doesn't preclude broader changes later; in fact, this change would encourage them).
Motivation for break:
releaseassets have better performance (sometimes by a large margin).- Some CLI commands bias strongly to prod, which provides us with a good opportunity to provide a differentiated prod experience. We don't want developers deploying
debugassets into prod unwittingly. Differentiated experiences can help that.
dotnet build/publish produces RID-specific apps by default
Proposal: https://github.com/dotnet/sdk/issues/23540
The .NET SDK has produced portable apps since .NET Core 1.0. That may or may not have made sense, but it no longer does now. Portable apps are bigger, slower to startup, and less reliable in some scenarios. In addition, portable apps are not fully coherent since they have a RID-specific executable but portable assets. That means that you can use the executable for one RID environment and cannot for any other. It's an odd design choice. A perfect example is containers. RID-specific apps are always better for containers. Another example is client apps. Client apps require an executable, such that they should always be RID-specific.
Motivation for break:
- Portable apps are good for a small subset of scenarios.
- RID-specific apps are the only good option for a variety of popular scenarios.
dotnet build/publish does not produce an exe/apphost for portable apps by default
Proposal: https://github.com/dotnet/sdk/issues/23545
Portable apps are intended to run in multiple environments. By definition, an executable is RID-specific and therefore only compatible with one of the environments in which a portable app can run. It's possible that developers are happy with this asymmetry, but should opt into that experience.
Motivation for break:
- The apphost makes portable apps incoherent.
- It is easier to explain how to use portable apps in absence of an executable apphost.
Precompile with AVX2 (x64) or NEON (Arm64) instructions for better startup performance
Proposal: https://github.com/dotnet/designs/pull/173
Vector (SIMD) instructions are now one of the key performance pillars of the .NET platform. Today, pre-compiled Ready-to-Run (R2R) code targets the SSE2 instruction set on x64. We require the JIT to tier the platform to tier 1 in order to take advantage of larger/newer vector instructions (like AVX2). That model penalizes startup on modern hardware and also places a hard dependency on tiered compilation for good performance. Ideally, R2R code was already very good, and tiered compilation was reserved for only the highest value methods. We can achieve that by compiling R2R code with AVX2 (x64) and NEON (Arm64) by default. We would not change the Windows 32-bit or Arm 32-bit builds.
Note: After this change, machines with AVX2 will have better performance, while machines w/o it will end up with worse performance since some R2R methods will be rejected and require jitting where they previously did not.
Motivation for break:
- Highest performance, including at startup.
- Align product to modern hardware.
For additional context, SSE2-compatible hardware was first released in 2000, and the same for AVX2 in 2013.
/cc @vitek-karas, @jkotas, @marcpopMSFT, @baronfel, @tannergooding, @ericstj, @joeloff, @dleeapho, @mangod9
Overall LGTM.
One nit is that "Require AVX2 (x64) or NEON (Arm64) hardware for best performance" is a confusing statement. We aren't requiring AVX2 and we will continue working on hardware without. Instead we are assuming that the hardware will have AVX2 support by default and will prep our assemblies using crossgen that assumes AVX2 is available. This will provide faster startup and steady state throughput on modern hardware but will force older hardware to reject the impacted R2R method entries and fallback to jitting them instead.
For ARM64, Neon (otherwise known as AdvSimd) is already considered baseline and there should be no "break" here.
Also worth noting that the below is inaccurate:
For additional context, SSE2-compatible hardware was first released in 2004, and the same for AVX2 in 2013.
For reference, SSE2 was introduced in 2000 alongside the Pentium 4. It's been required on all x64 CPUs which were first introduced in 2003. AVX2 was introduced in 2013 and has been available on most CPUs since then. There are some low-end/budget CPUs (like older Intel Atoms) that have shipped since then without AVX2 support, but they are believe to be a minority overall.
I added a note to clarify and updated the dates. Helps?
Yes. I think the wording on the title is the most confusing part, however, and the concern is that its going to show up in some "clickbait" blog post somewhere.
I think changing:
Require AVX2 (x64) or NEON (Arm64) hardware for best performance
to
Precompile with AVX2 (x64) or NEON (Arm64) support for better startup performance
might be better. Thoughts?
Done.
Thanks for putting this together. I'm aware of and supportive of most of these. For the SDK implicit RID, I assume that the proposal once written will include the locations we want to enable the default?
I'd need to see the proposal for portable to weigh in on that one as I don't know enough about what our customer expectations are and whether this would cause more confusion than it would save.
Yup. Will do what you've asked.
Thank you for the PATH writeup @richlander
Another wording one:
dotnet build/publish does not produce an exe/apphost for portable apps by default
how about:
dotnet build/publish for portable apps, by default no longer produces an exe/apphost
Seems like passive construction, right?
@marcpopMSFT -- all of the proposals now have lengthy write-ups. Tell me if that works.
I just added another one on release mode.
Some of these changes will potentially impact ASP.NET Core scenarios more than console/client scenarios. Indeed some of the existing default behaviors are there because the first version of .NET Core only had ASP.NET Core as a primary workload. Can we please ensure we explicitly loop in folks from the ASP.NET Core & VS web tooling sides to weigh in on all these proposals.
@Tratcher @vijayrkn @sayedihashimi @davidfowl
Yes. publish is the one that was mostly there for ASP.NET Core.
I'm not a fan of the current build/publish dichotomy. Ideally, we'd make this proposed change (release for publish by default), and use that as a way to (re-)start the bigger conversation while not blocking on delivering value (even if the final changes makes this change moot).
@richlander The problem is that the proposal doesn't say much about how ASP.NET Core uses publish and what the alternative is. The details are a bit anemic on those scenarios at the moment...
I may be missing something, but we're just talking about whether the developer has to do one of the following to opt-out of default behavior (current vs proposed behavior):
dotnet publish -c Releasedotnet publish -c Debug
Is it more complicated that that?
I didn't mention ASP.NET Core since I think of it as the primary use case for publish. As a result, I was primarily thinking about ASP.NET Core.
I also wrote up some more insight on my RID-specific-thinking. https://github.com/dotnet/sdk/issues/23540#issuecomment-1019568528
cc: @nohwnd
@richlander ie recommend sitting with the asp.net stakeholders so you can make sure the proposal isn’t missing some important pieces (I think it is).
The no exe/apphost by default is an interesting change, given iirc .NET Core 1.0 also didn’t do this by default but the exe/apphost was added later (presumably for a reason).
Is the reasoning for originally adding the exe/apphost by default documented somewhere for reference?
We wanted exe/apphost for fx-dependent apps from the start but didn't prioritize that. We did that in .NET Core 3.0. That's the same time we added Windows client apps, which require an exe. That explains the timing. It would have been strange to only do that for client apps.
@Perksey Note that the end result of Rich's proposal is still to almost always have an apphost. If apps are RID-specific by default, and RID-specific apps have an apphost by default, most everything has an apphost. It's only portable apps, which will be moved behind an "any" RID, that won't have one.
Yup. These topics were have largely not been re-assed since they were first designed. I've been working on improving this area for multiple releases (largely failing). Here's a proposal I wrote in 2019 (that didn't go anywhere) to provide some more insight. https://gist.github.com/richlander/2c8614825f9a289109ce4fd7e9ceceeb
The change to RID specific by default makes me the most nervous. I'll need to think on it more but by gut reaction is not in favor.
We have many developers that build on Windows and deploy to Linux (non-container) environments. Right now they don't have to think anything about this because .NET is portable by default. This is commonly developers less experienced for Linux but want to use our managed service for Linux hosting to reduce cost. With this change we would have to train the potentially less advanced developers how to use non-default settings to get their application deployed correctly. Seems like we are raising the complexity bar for .NET instead of trying to make lives easier.
Would this breaking change happen if they are using .NET 7 SDK or only targeting .NET 7 and above. Meaning if I have the .NET 7 SDK but still targeting .NET 6 would I be affected?
I assume for .NET CLI tools we would have to force the non-default setting to make the tool portable?
Also for defaulting the RID value what is the SDK's behavior if it can't determine the RID? Will the build fail, default to some generic RID or fallback to portable?
Since right now there is a --runtime switch I would assume this gets morphed into how to change the default computed RID. Would a new switch be added to indicate RIDless or have a special value for --runtime?
This is similar to @DamianEdwards concern. One option is that ASP.NET Core templates to set a property for portable apps. We'd also make it easy to reverse the setting in containers.
You could also ask why make ASP.NET Core apps deal with this, and instead get client apps to opt into RID-specific behavior, since they seem to be the one that want it most.
This all comes down to philosophy. We either make the the platform native-like (like Go) or more dynamic-like (like Node.js) at its core. I'm clearly in the native-like camp. I think dynamic-like as base behavior is a losing proposition for .NET. For example, .NET doesn't even have a good REPL. .NET has so much to offer by having so much capability, but today the CLI/build defaults are a strange mix w/o a clear personality or motivation. That's the part I'm wanting to fix, by making the defaults very clearly native-like and making it easy to opt-in to the dynamic-like behavior. That should make everyone happier.
Would this breaking change happen if they are using .NET 7 SDK or only targeting .NET 7 and above. Meaning if I have the .NET 7 SDK but still targeting .NET 6 would I be affected?
Good q. I didn't state that, but I'm thinking we'd tie this to TFM, so .NET 7 SDK targeting .NET 6 TFM would NOT see a change in behavior.
I assume for .NET CLI tools
Good call. Yes, tools should remain portable. There is an excellent reason for that behavior.
Also for defaulting the RID value what is the SDK's behavior if it can't determine the RID?
This never happens. The SDK always knows it's RID. If it doesn't, that's a more general product failure and many other things won't work.
rich@MacBook-Air ~ % dotnet --info | grep RID
RID: osx-x64
Would a new switch be added to indicate RIDless or have a special value for
--runtime
The any RID already means this. We could potentially add a CLI switch of --portable to be a convenience on that.
@richlander, great to see the MLL changes listed here. Thanks!
@richlander We have removed the .NET 5 FileStream compatibility mode. @danmoseley suggested to me that we should list it as a breaking change as well. I've added a description that you could reuse here: https://github.com/dotnet/runtime/issues/55196#issue-937671447
@richlander @agocke Makes sense, thanks for that. :)
We had a first sync on these today. I'll share plans when we get a bit further.
I asked if anyone wanted to defend multi-level-lookup. No one did. It feels so sad and abandoned. So sad.
Precompile with AVX2 (x64) or NEON (Arm64) instructions for better startup performance
- How much of an improvemrnt are we talking about?
- How much of a detraction if AVX2 isn't available?
SSE2-compatible hardware was first released in 2000, and the same for AVX2 in 2013.
- is there any justification to keep SSE2 as an allowed target and make AVX2 a new default. So if need be, a developer has the power? Just thinking that for the desktop consumer market where home users may be running Winforms / WPF & in future - MAUI apps on decades old pc's perhaps removal of the SSE2 support would introduce perf losses for those users and not gains? I am guessing this is not the primary use case for this feature though, and it is more for enabling cloud infra that runs millions of app startups per day to use hardware more effectively in data centres?
We're still working on the numbers (improvements and regressions). You are right that this feature is motivated by cloud infra. One options is to make this change for Windows Arm64, Linux Arm64/x64 and macOS Arm64 only, and leave Windows x86/x64, and macOS x64 as-is. That would skirt most of the risk, although it would be unfortunate if our Windows x64 cloud build was unimproved. All topics to work through when we have better numbers.
is there any justification to keep SSE2 as an allowed target and make AVX2 a new default. So if need be, a developer has the power? Just thinking that for the desktop consumer market where home users may be running Winforms / WPF & in future - MAUI apps on decades old pc's perhaps removal of the SSE2 support would introduce perf losses for those users and not gains? I am guessing this is not the primary use case for this feature though, and it is more for enabling cloud infra that runs millions of app startups per day to use hardware more effectively in data centres?
To be clear, SSE2 support isn't being removed, it will just not be the target for the pre-jitted versions of the BCL libraries
Effectively all of the binaries we ship in the BCL are "pre-jitted" for a given baseline. This means on a typical application startup, these methods do not have to be jitted they can just be used "as-is". If they are executed more than ~30 times then Tiered Compilation will kick in and they will be rejitted for "your current hardware", improving overall application throughput because they are known to be "hot" (or frequently called methods).
Our previous baseline was SSE2 and this meant that startup was decently fast on all computers, but it also means that many programs needed rejit for "core" methods in Span, Array, and string to ensure that they are "optimally accelerated" for their hardware.
We are changing the new baseline to AVX2, which means that for essentially any computer released in the last decade will already get code that is "optimally accelerated" (this also puts this roughly "on parity" with what ARM64 hardware provides out of the box). If you happen to be on hardware without AVX2 support then your application startup will take longer because the pre-jitted code will be rejected and the method will have to be jitted before execution can start.
This should only impact startup and only for methods that actually use AVX/AVX2 instructions. If the method is "simple" and doesn't utilize any AVX/AVX2 instructions (this is primarily floating-point and SIMD instructions such as from System.Numerics.Vector2/3/4, System.Numerics.Vector<T>, or System.Runtime.Intrinsics.*; but also extends to some stack zeroing logic for methods with many locals) then there will be no need for the method to be rejitted as it isn't using any instructions that would prevent it from running on a given user's hardware.