-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Proposed .NET 7 Breaking Changes #7131
Comments
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 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:
to
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:
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 |
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. |
Yes. I'm not a fan of the current |
@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):
Is it more complicated that that? I didn't mention ASP.NET Core since I think of it as the primary use case for I also wrote up some more insight on my RID-specific-thinking. dotnet/sdk#23540 (comment) |
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 Since right now there is a |
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.
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.
Good call. Yes, tools should remain portable. There is an excellent reason for that behavior.
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
The |
@richlander, great to see the MLL changes listed here. Thanks! |
@richlander We have removed the .NET 5 |
@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. |
|
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. |
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 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 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 |
@richlander for the PATH issue, because it's set through the host the fix in .NET 7 may not be sufficient unless we have the same change in 3.1, 5.0, 6.0. Or we should just be very clear that if you only have .NET 7, you'll be fine, but once you mix previous versions, the issue can still surface. |
@richlander That is interesting debate to put .NET in the native Go/Rust trajectory by default. Would that make AOT being mainstream an eventual goal for .NET. If the goal that is to make .NET a native platform should R2R also be done by default in .NET 7? |
I'd like an x64 to perform well as the x86 runtime does. |
@jssmotta what version of .NET are you using? @davidfowl is this expected for ASP.NET? |
Yes and no. My motivation isn't oriented around AOT. Instead, I see that the defaults are not really a coherent personality. To some degree, I don't really care whether the defaults are node-like or go-like, as long as they strongly point in one direction and it is easy to switch. If I look at the broad set of investments we've made (and expect to make) and what we are asked for, they are much more native-learning than dynamic-leaning. Also, I think .NET does a better job providing value and aligning with experiences with folks who prefer native-leaning platforms. The beauty of .NET is that you can get a very nice range of experiences, depending (primarily) on your build configuration. I see the fact that my proposal aligns with AOT as a nice bonus. Our UX needs/challenges and yours are very similar. I'm going to take a few days to look at some more ASP.NET UX to see if I can find a good option. |
|
How are these users deploying to Linux? Is it manually or via a publish action in Visual Studio? I am assuming that when building with an implicit RID, we'll have the same output directory structure as for explicit RID, so you get something like bin/debug/net7.0/win10-x64, so it would pretty clear what the app has been built for. |
Hello, I make the correct build for each scenario, as you ask for 'bin/debug/net6.0/win10-x64' and x86. And my apps do not target Linux and I even know how to make such tests. So I bet this could be an issue in Microsoft OS or intrinsic of the architecture of .NET since .NET 4.x to .NET 5, .NET 6. I did not say before, but .NET 5 had this same slow behavior on the x64 bin. |
This is the one scenario that would be made worse by the proposal. One option we're playing with is leaving ASP.NET Core as portable by default and make it easy to switch. The other idea is to make all app-types RID-specific by default and make the ASP.NET Core publish profile experience portable by default. Do your apps use publish profiles? Or just |
@samsp-msft I know our AWS tooling for VS right now does a portable publish that can be used for deploying to either Windows or Linux AWS Elastic Beanstalk environments (WebApp hosting without containers). There is no reason we can't update that code to be RID aware for the target deployment. Our tooling for Lambda (Serverless functions) has always created deployment bundles using the RID for all of the reasons stated here, we need to get the startup time to be as fast as possible. |
@richlander That will be interesting challenge to explain why generic console is RID specific but ASP.NET Core is portable. Aren't a lot of the scenarios you are talking about for the new client side targets like Maui which already have platform specific TFW. Would that work to be RID specific if the TFW is platform specific. The exception I can think to that is Blazor WASM but I assume you could do something special for that since it targets its own specific SDK ( To be clear I'm not hugely opposed to the change. I just worry because to me this is a pretty big redefinition of .NET being a portable VM platform that optionally has some performance knobs to tweak things with tradeoffs. And with all of the variant Linux RIDs I can see this potentially causing havoc with Linux build system of one variant (Ubuntu) being deployed to another Linux variant (Alpine). This could cause subtle issues with which third party native dependencies get bundled up that could be a pain to track down. |
That's where we started since we only had web and console. Also, that was before containers became big, where portability is (for the most part) adds unnecessary cost. So, yes, that's what I'm pushing to change. The platform won't change. I just consider portability to be a more advanced scenario that people should opt into.
This may be a stretch, but this isn't really "knobs with tradeoffs". For all client scenarios, portable only offers cost w/no benefits. It's these "non-specific web deploys" that we still need to reason about. |
Ah, I couldn't find this doc earlier (and convinced myself I hadn't written it). It provides more insight into my thinking. https://github.com/dotnet/designs/blob/main/accepted/2021/architecture-targeting.md |
@richlander
I humbly accept your challenge.
Perhaps technically possible.. possibly not technically desirable. Fun challenge aside, I assume that the default UX for library projects would still be that a build outputs a single binary targeting |
Yes. |
Does |
I meant desktop/mobile client. You are right on console. I also use that experience. The only conversation on the table is whether that will be the default vs opt-in. It definitely won't be taken away. In any case, I still have to write-up a plan that works through all the workflows. I'm writing a different spec at the moment, but will do that next. |
I am also concerned about this proposed change. Here are my thoughts on it:
EDIT: I'd like to add that for those developing in a Windows environment and publishing to Docker (Linux), the change in behavior would definitely affect anyone following this tutorial. |
It should make perfect sense to also enable more advanced ARM64 instructions by default for RID=osx-arm64, because we already know that M1 is a baseline, e.g. we should enable int Test(ref int a, int b, int c) =>
Interlocked.CompareExchange(ref a, b, c); Currently we prejit (r2r) it like this: G_M59805_IG02: ;; offset=0008H
885FFC64 ldaxr w4, [x3]
6B02009F cmp w4, w2
54000061 bne G_M59805_IG03
8800FC61 stlxr w0, w1, [x3]
35FFFF80 cbnz w0, G_M59805_IG02
;; size=20 bbWeight=1 PerfScore 8.50
G_M59805_IG03: ;; offset=001CH
D5033BBF dmb ish
2A0403E0 mov w0, w4 if prejitted with G_M65452_IG01: ;; offset=0000H
2A0303E0 mov w0, w3
88E0FC22 casal w0, w2, [x1] Apple already do the same with its |
donet pack --output in 6.0.300+ BREAKS with |
@stunney are you meaning the breaking change described here? If so this was in 7.0.200 so I'm confused why you're seeing it in 6.0.300+. This was a regression and should be fixed in 7.0.201, which I was able to use in GitHub Actions. If you have a pointer to an erroring project or CI pipeline we could investigate further? |
The following list defines breaking changes that are proposed for .NET 7.
Implemented:
Planned for .NET 7:
Release
withPublishRelease
sdk#23551Unclear plan:
Moved to .NET 8:
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: dotnet/runtime#67022
Notice: dotnet/docs#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, viaDOTNET_ROOT
. When enabled, MLL expands the search space for runtimes and SDKs to include the global .NET installation location in addition to theDOTNET_ROOT
location. This can result in a runtime or SDK being selected from the global installation location rather than the intended privateDOTNET_ROOT
location, whether that is desired or not.Motivation for break:
Stop adding 32-bit .NET to the
PATH
for .NET 7+ runtimes and SDKs (on x64 machine)Proposal: dotnet/sdk#22030
Change: dotnet/runtime#69902
Notice: dotnet/runtime#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 finddotnet
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 thePATH
works. In short, it doesn't work well. Going forward, we should only ever add the OS native-architecture .NET install location to thePATH
. We already started doing that on Apple M1 (Arm64) machines (we only add the Arm64 .NET to thePATH
, not the x64 version). We need to repeat this pattern with Windows now, with the 32-bit .NET build.Motivation for break:
PATH
is incoherent, results in bad UX, and is difficult to explain.dotnet build/publish uses the implicit SDK RID for RID-specific apps by default
Proposal: dotnet/sdk#23539
Change: dotnet/sdk#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:
dotnet publish/pack produce release assets by default
Proposal: dotnet/sdk#23551
Our basic guidance to developers since .NET Core 1.0 has been "use
build
for development andpublish
for prod". Given that, it would make a lot more sense ifpublish
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 ofbuild
andpublish
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:
release
assets have better performance (sometimes by a large margin).debug
assets into prod unwittingly. Differentiated experiences can help that.dotnet build/publish produces RID-specific apps by default
Proposal: dotnet/sdk#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:
dotnet build/publish does not produce an exe/apphost for portable apps by default
Proposal: dotnet/sdk#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:
Precompile with AVX2 (x64) or NEON (Arm64) instructions for better startup performance
Proposal: dotnet/designs#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:
For additional context, SSE2-compatible hardware was first released in 2000, and the same for AVX2 in 2013.
The text was updated successfully, but these errors were encountered: