-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
NativeAOT status for Android #106748
Comments
Maybe just to breakdown the work involved slightly:
We did some of the basic groundwork in .NET 9, such as:
This seems like a multi-month effort involving multiple teams. I don't actually know when we'd start on this; as it's quite above my paygrade. |
I'm somewhat skeptical of this. We've increasingly stopped doing things more specific than
Agreed that this is necessary, but somewhat ill-defined, I think. It's not clear what functionality is available in Mono that isn't available in Core CLR. |
There are number of special cases for Android in the higher-level runtime libraries. For example: Lines 20 to 22 in 477de34
These special-cases are unnecessary to get ordinary Linux-targeting code running on Android, but they are necessary for compatibility with Xamarin Android behaviors that exist today.
Yes, the first step would be to extract the required functionality into an API proposal. The APIs that we have introduced for GC integration with ObjectiveC show the general shape to follow. |
So... The ETA is prob not .NET 10, right? |
Aside from the things mentioned earlier, the whole Android crypto interop is currently not part of the linux-bionic packages.
I had an idea to implement it in a way similar to Objective-C interop that I discussed informally with some of the stakeholders. Here's the rough version copied from communication logs:
Notably, I had some feedback on it and there may be additional problems with the approach that I didn't originally foresee (#104272 (comment)). We also didn't get anywhere near to implementing it, even as a rough prototype. |
I stand corrected. I find this factoring pretty unfortunate, though. |
Why doesn't Microsoft continue to invest more manpower in optimization on monovm? |
There can be non-trivially duplicated task for optimization - sometimes even totally rework from scratch to make sure the architecture is optimal. NativeAOT was built from scratch to make everything AOT friendly. RyuJIT was built from scratch to replace the old JIT which originates from MSVC. |
Yes, you are right, but I hope development team can pay more attention to it, monovm feature and optimization always delay, even ignore, they always said it's not important. hope it can attach importance to align coreclr. |
I have opend so many issse https://github.com/dotnet/runtime/issues/created_by/srxqds ,most of them are igored. |
Additional information: NativeAOT for Android was experimented here: https://github.com/dotnet/runtimelab/tree/feature/nativeaot-android When you look at the section "Performance measurements", take it with a grain of salt. In Discord the following was mentioned when this document was released:
The size and performance of devices "Pixel 7a" and "Emulator" got updated after this message but I'm not sure about "Samsung Galaxy S10 Lite", Samsung "Galaxy S23" and "Pixel 5". These numbers didn't change after the initial commit (see Git Blame).
CoreCLR was made from the ground. Mono has to adapt to CoreCLR which can make it harder. For example, generics are currently a problem in Mono while more generics are being used in libraries:
Mono has multiple backends; MonoJIT, MonoInterpreter, MonoLLVM (maybe I'm missing more) so implementing performance improvements is quite the task. |
@GerardSmit do you know what the Avalonia team used for this video https://www.reddit.com/r/dotnet/comments/13lvih2/nativeaot_ndk_vs_xamarinandroid_performance/? In their video the performance is as fast as what I get on iOS with NativeAOT. |
I also have a sample here, that should have been testing |
@vyacheslav-volkov I'm not sure what they used. I'm also not sure if they released any tools or source.
Which may be the reason they never open-sourced/released this experiment. |
At the risk of completely sidetracking this discussion, @vyacheslav-volkov wrote:
There are many parts of the stack, and the .NET for Android part of the stack is not that slow. On a Pixel 6 Pro:
This is the first time I launched these apps (no averaging or anything), and .NET for Android is 93ms slower. I don't consider that to be a lot of overhead. The problems you're observing are not solely in MonoVM or JIT or runtime or .NET for Android (or everything built atop of them). I would not expect NativeAOT to be a "silver bullet" either. |
The reason for this is pretty clear cut. Most / all of the crypto API's are Java API's and since linux-bionic is not including any of that (analogous to targeting the NDK), there's not much we can do outside of re-opening the discussion of shipping openssl as part of the runtime. |
@jonpryor I agree that an empty application starts up fairly quickly. However, once you start adding code, the startup time begins to drop dramatically. At this point, the startup time doesn't depend on actual code optimizations anymore; it all comes down to the JIT compilation speed and efforts to reduce it. For example, some advice suggests using only classes because it supposedly reduces compilation time #101135 (comment). But I can't say this is great advice, considering that everything in .NET is moving towards reducing allocations in the heap, and the framework itself is actively shifting towards using struct everywhere. Prohibiting the use of struct just for the sake of a faster startup sounds unreasonable. Or take a simple MAUI application: its startup time will be around 600 ms. This means that an empty application already starts 2-3 times slower than a native application. As the developer adds their code, the startup time ranges from 1500 to 5000+ ms. In this case, traditional code optimization doesn't work — the developer must understand that optimization here is about easing the JIT compilation process rather than improving the code itself. Here's a real example: my framework doesn't use any complex features, but it has a lot of struct and generic. The actual startup time with FullAOT on Android is about 1000 ms on a Galaxy Note 10. The same code on Xamarin.iOS with NativeAOT starts instantly on an iPhone X. Here's the link to the repository https://github.com/vyacheslav-volkov/PerfAndroidTest/tree/main, where you can find two projects — Android (FullAOT) and iOS (NativeAOT). I added .speedscope.json files for the Android project to the |
Here is another sample which uses NativeAOT on Android, and unlike @jonathanpeppers sample has the benefit of looking like .NET for Android, with a C# subclass of For comparison to the previous Android times:
Compare 300ms to Java (+141ms) and .NET for Android (+234ms). This also contains additional debug prints, so isn't directly comparable, but should further emphasize that NativeAOT in and of itself will not be a silver bullet to all of your startup woes. A lot depends upon code higher up the stack. At present, one of the primary blockers keeping us from dedicating more effort to NativeAOT support within .NET for Android is the lack of a decent GC story. (The current story is "everything leaks, lol".) I do not foresee dedicating significant effort to support NativeAOT on the .NET for Android side until after the GC story is complete, and I'd further guesstimate that we'd want at least one .NET release after the GC exists before we'd support it.
If NativeAOT has a GC story for .NET 10, I'd tentatively hope for preview support in .NET for Android by .NET 11. Maybe. (There are a number of unknown unknowns, and would not want to get anyone's hopes up.) Increase numbers as appropriate. |
This sounds right to me. For reference, the console app startup for native aot on a Linux desktop machine is measured in microseconds so the runtime overhead in native aot is ~0. All of the startup impact is the cost of the code running in the startup path. |
@jonpryor I just conducted a quick and rough test, but I think the point will be clear. I measured the initialization time of services in my test application twice, meaning the same code was executed twice. The first time it needed time for JIT compilation, and the second time it ran without JIT compilation. The second run was 27 times faster. As a developer, there’s nothing I can do to affect JIT compilation, and traditional code optimizations won’t work. I would need to rewrite the entire codebase just to make it easier for Android’s JIT compiler to handle it. And this is just a small portion of the code needed to launch the application. In this code, nothing is being called other than the creation and registration of services.
If NativeAOT can make any user code executes in 50-100 ms (based on this example, this is more than enough if we don't need JIT compilation), plus an additional runtime execution time of 250-300 ms, we would achieve a total startup time of 350-400 ms for any application. This is comparable to the startup time of native applications. |
@vyacheslav-volkov for your example above, have you tried either to "AOT Everything" with By default, we use a built-in AOT profile that won't include most of your code. It is a reasonable tradeoff for app size vs startup time. If you can use AOT for the code above, (note that this is using Mono's AOT in the current product, and completely unrelated from NativeAOT). |
@jonathanpeppers I've used this test project with FullAOT (Mono's AOT), you can check the config, it should be good: Out of curiosity, I ran the same code without AOT, and here’s the result:
|
@vyacheslav-volkov can you check it's actually using AOT? It seems odd AOT would make the first run worse than JIT.
This should make Mono print out a log message for each method like:
Note it's expected some methods will say:
Clear |
My observations and suggestion as an end user: Android is 2+ years behind other targets for AOT compilation in dotnet.
This stems from using the Android SDK which relies on Java interop, which makes things much more complicated than any other platform. The possible plans described above (#106748 (comment), #106748 (comment)) suggest tackling these issues which may take a long time. Surely the NDK is a better target Flutter uses the NDK and fully AOT-compiles everything and you can access relevant android-specific stuff from dart. This is where dotnet should be:
In dotnet, there are some POCs as mentioned above (https://www.reddit.com/r/dotnet/comments/13lvih2/nativeaot_ndk_vs_xamarinandroid_performance/ and more recently https://github.com/jonathanpeppers/Android-NativeAOT ), both using SkiaSharp. But we would need the NDK callable from dotnet, similar to calling native code from dotnet in SkiaSharp, dotnet-ios, WinUI, etc.. |
@emmauss If you guys can try running the demo with Mono debug log enabled & share the number of AOT_NOT_FOUND methods @ startup, it would be great... Maybe it will help MS folks to seriously think about the priority of this issue. Especially in the light of "you'll be lucky to have .NET Native for Android in .NET 11".
|
One note: a lot of the above costing implicitly assumes MAUI is on top, meaning that the system needs tight JVM integration. I don't know if platforms like Avalonia actually require that. If not, and they can compile against the Android NDK, the cost and schedules may change. I'll let someone from Avalonia speak on how they used Native AOT in the past. |
We tested Native AOT with the Android NDK, using NativeActivity for our activity. This cut support for SDK apis that modern android apps use, like the storage access framework, window insets, text and input composition and embedding native android views in-app. Apis we need for storage, window customization and text prediction support. Also, we couldn't use any dotnet android libraries. These do not make it appealing to end users as they will be cut off from the rich dotnet android library ecosystem, and also need to set up a lot of build scripts just to build and sign their app. |
Generally, I don't know how you would make a "real" Android application that without calling Java APIs. Even Unity3d games would use their Java interop support for things like in-app purchases, push notifications, etc. There are a lot of random OS features you have to access from Java, so I would think most Avalonia apps would also need to use these. |
I believe you could still call Java APIs through JNI, it would just be significantly more effort than the current implementations.
Agreed, the downside of this approach would be none of the existing Android/Java interop would work. |
@agocke If someone could fix this issue #101135, we could use it as a workaround until full support for NativeAOT is available. Could someone from the team assess how difficult this task might be and estimate how long it might take to fix? Currently, we do not have a truly working solution to the slow startup problem. |
That issue has 86 comments, so let me see if I can summarize. That's not one issue but really a blanket issue for: we've seen a variety of methods that must be JITed in our sample apps, which causes slow startup. Is that right? If so, I would expect that issue to verge on impossible to fix. Rearchitecting Mono to AOT everything is more expensive than just using Native AOT. |
No, it's not quite right: there is a very specific scenario where AOT code isn't generated for a generic method instance:
I'll find the link to the specific piece of code making all these checks a bit later (already shared it here). Overall, my impression is: yes, probably some extra is necessary to eliminate some of these constraints (e.g. modifying AOT code lookup logic, etc.), but this isn't as complicated as a full overhaul of Mono AOT. Moreover, I suspect some of these constraints were originally added to decrease the number of generic implementations AOT generates in Full mode, and it happened at the time when generics weren't so widespread + there was no profile-based AOT mode. |
I also wish people responding to it take it seriously right from the beginning instead of saying ~ "well, you guys are fine - the app is at least starting, right? as for the startup time, it's sad, but please wait for a few more years." I didn't write about this bizarre issue with AOT because I genuinely love .NET and believe you guys are doing a great job making it better. So even though this discovery means Mono AOT is 90% fake, and profile-guided AOT deserves this name only formally, I'd rather wait for MS to address it. And somehow... Somehow I discover that "it's fine" feels like an acceptable answer for MS here. But seriously, how it can be fine, if a single post about this would decrease a chance of MAUI being used by a given company by maybe 50%? Isn't an elevated ANR & Play Store penalization one of the worst things you can face, assuming you can't fix this? |
My point is: if you guys would run MAUI as a startup, this issue would be instantly classified as "existential":
|
Assuming the above are true, my understanding is that this is not quite as expensive as guaranteeing Mono can AOT any code, but it's close. My understanding is that specialization of value types is one of the main limitations in the current architecture and implementing it would be a very large work item. It's certainly possible that there is a simpler implementation I'm not aware of, but I would start the cost as very expensive. |
If a mono architecture limitation prevents full AOT on Android, why does full mono AOT work on IOS? |
The problem is that there is a huge gap emerging between iOS and Android in terms of performance for .NET applications. With each new release, the .NET team adds more and more value types When developers ask why it is so slow on Android, they are told that using value types is detrimental because it makes JIT compilation harder, and they should avoid using them. But developers who have already spent a lot of time writing their code with value types are unlikely to create a special version just for Android that avoids them. This situation reveals a contradiction: the entire .NET ecosystem is moving towards optimizations by reducing allocations, but if you want to write for Android, you are advised to forget about value types and generics and use only classes. I am currently working on a large project, and for iOS, I am using .NET Native. My library heavily utilizes generics and value types, and I see no issues with this. The installation size from the App Store is 72MB, which is smaller than many similar native applications written in Objective-C/Swift (~100MB), and the performance is comparable to these native apps. The situation is entirely different with Android. As soon as I add simple initialization that does nothing but call managed code, I experience a significant performance hit. For instance, JIT compilation slows down initialization by a factor of 27 check my comment #106748 (comment). I've also shared a repository where you can check it out https://github.com/vyacheslav-volkov/PerfAndroidTest. I have used all possible optimization methods, including FullAOT for Android, but this only slightly improves the result. The only option I see is to rewrite all the code specifically for Android, but even that may not help, as .NET itself uses many generics. I don’t understand the .NET team's stance, which denies this issue by citing empty applications where everything works fine. I have provided my examples and asked for optimization assistance (@jonathanpeppers @jonpryor) but have not received any real advice that could help address the situation. The most frustrating part is that there seems to be no hope for this issue to be resolved in the near future. Now, think about it: if someone is starting a project for mobile platforms today and is choosing a framework, and they find this issue, will they choose .NET if even for such a basic thing as startup time there is no solution? I am confident that if resources were allocated to address this issue, it wouldn't necessarily require immediate implementation of .NET Native AOT. It would be enough for someone with knowledge of Mono to try to solve this problem #101135 and provide an answer: why it really cannot be done or how it can be done and within what timeframe, as far as I understand MonoAOT works for some internal generics but doesn't work for custom generics, maybe fixing it isn't that hard. Currently, all discussions point out that Native AOT for Android is difficult, fixing MonoAOT for Android is difficult, and that the problem lies with developers because everything works fine in tests with an empty application. |
Do you happen to have size and startup performance numbers for Mono AOT on iOS for this project? Would the Mono AOT size and startup performance be acceptable on iOS if native AOT did not exist? |
I'm also curious how it's even possible to run Mono AOT apps on iOS w/o interpreter enabled, assuming it works the same way for both iOS and Android (and our findings for Android are correct). For the note, we use interpreter-only builds for iOS - that's because our initial attempts w/ full AOT failed there, but that was ~ 1+ year ago, when we knew way less details of how it works + didn't do anything to address the explosion of generic instances in |
I know I'm not the one you ask here, but in our case even interpreter-only mode startup performance is acceptable on iOS - and that's exactly what we use now. I shared the numbers, it's about 1.1s on iPhone 13 (interpreter-only) vs 1.8s on Galaxy S23 Ultra (both profiled and full AOT). |
@jkotas MonoAOT performs almost as fast as NativeAOT, but the application size becomes larger. I just checked, and for this project, it's around 108 MB for MonoAOT. This is a reasonable size, as I mentioned earlier; similar applications written in Objective-C/Swift occupy about the same amount of space. |
In fact a GC bridge for Java intero, .NET (runtime) interop and .NET (NativeAOT) interop would be great. For some time now, I have been working on a JNI framework for .NET that is fully compatible with NativeAOT, and there is even an example of its use on Android. However, from what I have observed, native Android applications go beyond JNI; it even seems that all of Java's functionality is encapsulated in Android's own native libraries. |
it is better to deprecate monoVm in the future, while monoInterpreter to be a preserved component for clr would be a descent result. |
Out of curiosity, why? dotnet/java-interop has a couple of samples running on NativeAOT; the current JNI underpinnings of .NET for Android can work with NativeAOT. The major problem is the GC, as mentioned elsewhere on this issue. |
When I thought about this, it was December 2021. I also made several static approaches to handling the API, but due to the .NET version at the time, I didn’t like the final result because it depended 100% on instances rather than types. It wasn’t until the advent of generic math that I was able to make sense of what I wanted. And that’s all; I believe no one would actually use it because, even though it’s friendly to use, its overloaded—precisely because I tried to make it friendly to use. I think the most notable aspect of that approach is how I avoided the use of reflection (or even how I used it) through generic math to bring in the definitions (methods, fields, constructors, and functions) and data types (primitives, arrays, classes, and interfaces), always focused on a reflection-free scenario. It doesn't use source generators, and everything is compiled statically. It has switches to trim down scenarios, and in general, it is usable (perhaps with many errors because only I maintain it) in any scenario that uses JNI or the invocation interface. |
Any update on this? Without AOT performance is pretty horrible starting apps. |
How far away is the dotnet Android team from being able to create a proposal, as IOS did in [NativeAOT] Low level API support for Objective-C scenarios - with GC API described in #44659) ? |
I had previously raised this topic in another issue #101135, but I want to create a separate discussion as I couldn't find a place to track the progress on this matter.
The most serious and long-standing issues with Xamarin.Android is the slow startup time for applications. If you search the internet for "Xamarin.Android slow startup," you'll find hundreds of discussions on this topic. Even with all possible optimizations, including MonoAOT compilation, the startup time remains slow, and even MonoAOT works incorrectly on Android #101135. This problem is particularly noticeable with UI frameworks such as Avalonia, UNO, and MAUI. Developers simply don't have the ability to solve this problem on their own, as it is rooted in the fundamental aspects of the platform's operation, and a significant amount of time is spent on JIT compilation. In the end, to write a "fast application" for Android that still lags behind native applications in terms of startup speed, you need to perform a whole range of additional operations, which not every developer can manage, just to make their application work somewhat faster. I believe that this expectation is where the main problem lies. A developer expects that the release build will immediately work as it should, but instead, they encounter performance issues where they don't expect them.
When .NET Native was introduced, I thought it would be the solution to the slow startup problem for Android. Starting with version .NET 8.0, it became stable for iOS, and I began actively using it. The results are impressive: a fairly large application on an iPhone X launches as quickly as any native application and even faster than a similar application on a Samsung Galaxy S22 Ultra, despite all possible optimizations for Android. The gap between the release of these devices is five years, and I dread to imagine the startup time on a five-year-old Android device. Yes, there are still limitations on using dynamic code, but they are not that difficult to overcome, resulting in an application that performs as fast as a native one. Isn't that what we want for a cross-platform application? Moreover, I’m almost 100% sure that no one uses Android applications without ProfiledAOT or FullAOT because, in that case, you can forget about startup performance. This also means they are already using trimming, so transitioning to NativeAOT wouldn't require much additional effort. Over time, more libraries and frameworks will become fully compatible with NativeAOT, making integration seamless for developers without any issues.
However, observing the discussions about .NET Native and the activity around this topic, I get the impression that the team does not give this problem enough priority, and no specific timelines have been set for its resolution. For example, in one of the discussions on GitHub, the following is mentioned:
This gives the impression that allocating resources for NativeAOT on Android is not a priority, and instead, new releases include optimizations that only provide marginal improvements (e.g., -10% startup time for test cases). However, in real-world conditions, such improvements do not solve the problem. If an application takes 2000ms to start, even reducing it to 1800ms makes little difference, and at best, such optimizations are noticeable only under ideal conditions.
It seems to me that the team does not fully grasp the depth of this issue. Many of my colleagues have already switched to Flutter specifically because of the slow startup times on Android. When their clients or customers ask why the Android application launches so slowly, developers are forced to reply that it is a limitation of the technology they are using, they may also suggest switching to iOS, where there are no such problems, but this is not an option.
In my opinion, the implementation of NativeAOT support for Android, should be considered critically important. I would like to hear the team's thoughts on this matter: what should we expect? Will NativeAOT support for Android be added in the near future, or should we only hope for small, incremental performance improvements that don't solve anything and are waiting for everyone to switch to Flutter?
The text was updated successfully, but these errors were encountered: