.NET MAUI 性能提升(上)
( 本文閲讀時間:20分鐘 )
.NET多平台應用程序UI (MAUI)將android、iOS、macOS和Windows API統一為一個API,這樣你就可以編寫一個應用程序在許多平台上本機運行。我們專注於提高您的日常生產力以及您的應用程序的性能。我們認為,開發人員生產率的提高不應該以應用程序性能為代價。
應用程序的大小也是如此——在一個空白的.NET MAUI應用程序中存在什麼開銷?當我們開始優化.NET MAUI時,很明顯iOS需要做一些工作來改善應用程序的大小,而android則缺乏啟動性能。
一個dotnet new maui項目的iOS應用程序最初大約是18MB。同樣,在之前的預覽中.NET MAUI在android上的啟動時間也不是很理想:
應用程序 |
框架 |
啟動時間 (ms) |
Xamarin.Android |
Xamarin |
306.5 |
Xamarin.Forms |
Xamarin |
498.6 |
Xamarin.Forms (Shell) |
Xamarin |
817.7 |
dotnet new android |
.NET 6 (早期預覽) |
210.5 |
dotnet new maui |
.NET 6 (早期預覽) |
683.9 |
.NET Podcast |
.NET 6 (早期預覽) |
1299.9 |
這是在Pixel 5設備上平均運行10次得到的結果。有關這些數字是如何獲得的,請參閲我們的maui-profiling文件。
我們的目標是讓.NET MAUI比它的前身Xamarin更快。很明顯,我們在.NET MAUI本身也有一些工作要做。dotnet new android 模板的發佈速度已經超過Xamarin.Android,主要是因為.NET 6中新的BCL和Mono運行時。
新的.NET maui模板還沒有使用Shell導航模式,但是計劃將其作為.NET maui的默認導航模式。當我們採用這個更改時,我們知道會對模板中的性能造成影響。
幾個不同團隊的合作才有了今天的成就。我們改進了Microsoft.Extensions ,依賴注入的使用,AOT編譯,Java互操作,XAML,.NET MAUI代碼,等等方面。
應用程序 |
框架 |
啟動時間 (ms) |
Xamarin.Android |
Xamarin |
306.5 |
Xamarin.Forms |
Xamarin |
498.6 |
Xamarin.Forms (Shell) |
Xamarin |
817.7 |
dotnet new android |
.NET 6 (MAUI GA) |
182.8 |
dotnet new maui (No Shell**) |
.NET 6 (MAUI GA) |
464.2 |
dotnet new maui (Shell) |
.NET 6 (MAUI GA) |
568.1 |
.NET Podcast App (Shell) |
.NET 6 (MAUI GA) |
814.2 |
**這是原始的dotnet new maui模板,沒有使用Shell。
內容十分豐富,來看是否有您期待的更新吧!
-
.NET Podcast:
http://github.com/microsoft/dotnet-podcasts
-
maui-profiling:
http://github.com/jonathanpeppers/maui-profiling
-
Shell:
http://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/?ocid=AID3045631
-
.NET Podcast App (Shell):
http://github.com/microsoft/dotnet-podcasts
主要內容
❖ 啟動性能的改進
-
在移動設備上進行分析
-
測量隨着時間的推移
-
Profiled AOT
-
單文件程序集存儲器
-
Spanify.RegisterNativeMembers
-
System.Reflection.Emit和構造函數
-
System.Reflection.Emit和方法
-
更新的Java.Interop APIs
-
多維Java數組
-
為android圖像使用Glide
-
減少Java互操作調用
-
將android XML移植到Java
-
刪除Microsoft.Extensions.Hosting
-
在啟動時減少Shell初始化
-
字體不應該使用臨時文件
-
編譯時在平台上計算
-
在XAML中使用編譯轉換器
-
優化顏色解析
-
不要使用區域性識別的字符串比較
-
懶惰地創建日誌
-
使用工廠方法進行依賴注入
-
懶惰地負載ConfigurationManager
-
默認VerifyDependencyInjectionOpenGenericServiceTrimmability
-
改進內置AOT配置文件
-
啟用AOT圖像的延遲加載
-
刪除System.Uri中未使用的編碼對象
啟動性能的改進
▌ 在移動設備上進行分析
我必須提到移動平台上可用的.NET診斷工具,因為它是我們使.NET MAUI更快的第0步。
分析.NET 6 android應用程序需要使用一個叫做dotnet-dsrouter的工具。該工具使dotnet跟蹤連接到一個運行的移動應用程序在android, iOS等。這可能是我們用來分析.NET MAUI的最有影響力的工具。
要開始使用dotnet trace和dsrouter,首先通過adb配置一些設置並啟動dsrouter:
adb reverse tcp:9000 tcp:9001
adb shell setprop debug.mono.profile '127.0.0.1:9000,suspend'
dotnet-dsrouter client-server -tcps 127.0.0.1:9001 -ipcc /tmp/maui-app --verbose debug
下一步啟動dotnet跟蹤,如:
dotnet-trace collect --diagnostic-port /tmp/maui-app --format speedscope
在啟動一個使用-c Release和-p:androidEnableProfiler=true構建的android應用程序後,當dotnet trace輸出時,你會注意到連接:
Press <Enter> or <Ctrl+C> to exit...812 (KB)
在您的應用程序完全啟動後,只需按下enter鍵就可以得到一個保存在當前目錄的*.speedscope。你可以在http://speedscope.app上打開這個文件,深入瞭解每個方法在應用程序啟動期間所花費的時間:
在android應用程序中使用dotnet跟蹤的更多細節,請參閲我們的文檔。我建議在android設備上分析Release版本,以獲得應用在現實世界中的最佳表現。
-
在移動設備上進行分析:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#profiling-on-mobile?ocid=AID3045631
-
dotnet-dsrouter:
http://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter?ocid=AID3045631
-
我們的文檔:
http://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/tracing.md
▌ 測量隨着時間的推移
我們在.NET基礎團隊的朋友建立了一個管道來跟蹤.NET MAUI性能場景,例如:
-
包大小
-
磁盤大小(未壓縮)
-
單個文件分類
-
應用程序啟動
隨着時間的推移,這使我們能夠看到改進或迴歸的影響,看到dotnet/maui回購的每個提交的數字。我們還可以確定這種差異是否是由xamarin-android、xamarin-macios或dotnet/runtime中的變化引起的。
例如,在物理Pixel 4a設備上運行的dotnet new maui模板的啟動時間(以毫秒為單位)圖:
注意,Pixel 4a比Pixel 5要慢得多。
我們可以精確地指出在dotnet/maui中發生的迴歸和改進。這對於追蹤我們的目標是非常有用的。
同樣地,我們可以在相同的Pixel 4a設備上看到.NET Podcast應用隨着時間的推移所取得的進展:
這張圖表是我們真正關注的焦點,因為它是一款“真正的應用”,接近於開發者在自己的手機應用中看到的內容。
至於應用程序大小,它是一個更穩定的數字——當情況變得更糟或更好時,它很容易歸零:
請參閲dotnet-podcasts#58, Android x# 520和dotnet/maui#6419瞭解這些改進的詳細信息。
-
測量隨着時間的推移:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#measuring-over-time?ocid=AID3045631
-
.NET Podcas:
http://github.com/microsoft/dotnet-podcasts
-
dotnet-podcasts#58:
http://github.com/microsoft/dotnet-podcasts/pull/58
-
Android x# 520:
http://github.com/xamarin/AndroidX/pull/520
-
dotnet/maui#6419:
http://github.com/dotnet/maui/pull/6419
▌ 異形AOT
在我們對.NET MAUI的初始性能測試中,我們看到了JIT(及時)和AOT(提前)編譯的代碼是如何執行的:
應用 |
JIT 時間(ms) |
AOT 時間(ms) |
dotnet 新maui |
1078.0ms |
683.9ms |
每次調用c#方法時都會發生JIT處理,這會隱式地影響移動應用程序的啟動性能。
另一個問題是AOT導致的應用程序大小增加。每個.NET程序集都會在最終應用中添加一個android本地庫。為了更好地利用這兩個世界,啟動跟蹤或分析AOT是Xamarin.Android當前的一個特性。這是一種AOT應用程序啟動路徑的機制,它顯著提高了啟動時間,而只增加了適度的應用程序大小。
在.NET 6版本中,這是完全有意義的默認選項。在過去,使用Xamarin.Android進行任何類型的AOT都需要Android NDK(下載多個gb)。我們在沒有安裝android NDK的情況下構建了AOT應用程序,使其成為可能。
我們為 dotnet new android, maui,和maui-blazor模板的內置配置文件,使大多數應用程序受益。如果你想在.NET 6中記錄一個自定義配置文件,你可以試試我們的實驗性的Mono.Profiler. Android包。我們正在努力在未來的.NET版本中完全支持記錄自定義概要文件。
查看xamarin-Android#6547和dotnet/maui#4859瞭解這個改進的細節。
-
Profiled AOT:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#profiled-aot?ocid=AID3045631
-
啟動跟蹤或分析AOT:
http://devblogs.microsoft.com/xamarin/faster-startup-times-with-startup-tracing-on-android/?ocid=AID3045631
-
Mono.Profiler. Android:
http://github.com/jonathanpeppers/Mono.Profiler.Android
-
xamarin-Android#6547:
http://github.com/xamarin/xamarin-android/pull/6547
-
dotnet/maui#4859:
http://github.com/dotnet/maui/pull/4859
▌ 單文件程序集存儲器
之前,如果你在你最喜歡的zip文件實用程序中查看Release android .apk內容,你可以看到.NET程序集位於:
assemblies/Java.Interop.dll
assemblies/Mono.android.dll
assemblies/System.Runtime.dll
assemblies/arm64-v8a/System.Private.CoreLib.dll
assemblies/armeabi-v7a/System.Private.CoreLib.dll
assemblies/x86/System.Private.CoreLib.dll
assemblies/x86_64/System.Private.CoreLib.dll
這些文件是通過mmap系統調用單獨加載的,這是應用程序中每個.NET程序集的成本。這是在android工作負載中用C/ c++實現的,使用Mono運行時為程序集加載提供的回調。MAUI應用程序有很多程序集,所以我們引入了一個新的$(androidUseAssemblyStore)特性,該特性在Release版本中默認啟用。
在這個改變之後,你會得到:
assemblies/assemblies.manifest
assemblies/assemblies.blob
assemblies/assemblies.arm64_v8a.blob
assemblies/assemblies.armeabi_v7a.blob
assemblies/assemblies.x86.blob
assemblies/assemblies.x86_64.blob
現在android啟動只需要調用mmap兩次:一次是assemblies.blob,第二次是特定於體系結構的Blob。這對帶有許多. net程序集的應用程序產生了明顯的影響。
如果你需要檢查編譯過的android應用程序中這些程序集的IL,我們創建了一個程序集存儲讀取器工具來“解包”這些文件。
另一個選擇是在構建應用程序時禁用這些設置:
dotnet build -c Release -p:AndroidUseAssemblyStore=false -p:Android EnableAssemblyCompression=false
這樣你就可以用你喜歡的壓縮工具解壓生成的.apk文件,並使用ILSpy這樣的工具來檢查.NET程序集。這是一個很好的方法來診斷修剪器/鏈接器問題。
查看xamarin-android#6311瞭解關於這個改進的詳細信息。
-
單文件程序集存儲器:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#single-file-assembly-stores
-
mmap系統調用:
http://man7.org/linux/man-pages/man2/mmap.2.html
-
mmap:
http://man7.org/linux/man-pages/man2/mmap.2.html
-
程序集存儲讀取器:
http://github.com/xamarin/xamarin-android/tree/main/tools/assembly-store-reader
-
ILSpy:
http://github.com/icsharpcode/ILSpy
-
xamarin-android#6311:
http://github.com/xamarin/xamarin-android/pull/6311
▌ Spanify RegisterNativeMembers
當用Java創建c#對象時,會調用一個小型的Java包裝器,例如:
public class MainActivity extends Android.app.Activity
{
public static final String methods;
static {
methods = "n_onCreate:(LAndroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n";
mono.Android.Runtime.register ("foo.MainActivity, foo", MainActivity.class, methods);
}
方法列表是一個以\n和:分隔的Java本機接口(JNI)簽名列表,這些簽名在託管的c#代碼中被重寫。對於在c#中重寫的每個Java方法,您都會得到一個這樣的方法。
當實際的Java onCreate()方法被調用為一個android活動:
public void onCreate (Android.os.Bundle p0)
{
n_onCreate (p0);
}
private native void n_onCreate (Android.os.Bundle p0);
通過各種各樣的魔術和手勢,n_onCreate調用到Mono運行時,並調用c#中的OnCreate()方法。
拆分\n和:-分隔的方法列表的代碼是在Xamarin早期使用string.Split()編寫的。可以説,Span<T>在那時還不存在,但我們現在可以使用它!這提高了任何繼承Java類的c#類的成本,因此這是一個比.NET MAUI更廣泛的改進。
你可能會問,“為什麼要使用字符串呢?”使用Java數組似乎比分隔字符串對性能的影響更大。在我們的測試中,調用JNI來獲取Java數組元素,性能比字符串差。Split和Span的新用法。對於如何在未來的.NET版本中重新構建它,我們有一些想法。
除了.NET 6之外,針對當前客户Xamarin. Android的最新版本也附帶了這一更改。
查看xamarin-android#6708瞭解關於此改進的詳細信息。
-
Spanify.RegisterNativeMembers:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#spanify-registernativemembers?ocid=AID3045631
-
Java本機接口(JNI):
http://en.wikipedia.org/wiki/Java_Native_Interface
-
Span:
http://docs.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay?ocid=AID3045631
-
xamarin-android#6708:
http://github.com/xamarin/xamarin-android/pull/6708http://github.com/xamarin/xamarin-android/pull/6708
▌ System.Reflection.Emit和構造函數
在使用Xamarin的早期,我們有一個從Java調用c#構造函數的有點複雜的方法。
首先,我們有一些在啟動時發生的反射調用:
static MethodInfo newobject = typeof (System.Runtime.CompilerServices.RuntimeHelpers).GetMethod ("GetUninitializedObject", BindingFlags.Public | BindingFlags.Static)!;
static MethodInfo gettype = typeof (System.Type).GetMethod ("GetTypeFromHandle", BindingFlags.Public | BindingFlags.Static)!;
static FieldInfo handle = typeof (Java.Lang.Object).GetField ("handle", BindingFlags.NonPublic | BindingFlags.Instance)!;
這似乎是Mono早期版本遺留下來的,並一直延續到今天。例如,可以直接調用RuntimeHelpers.GetUninitializedObject()。
然後是一些複雜的System.Reflection.Emit用法,並在
System.Reflection.ConstructorInfo中傳遞一個cinfo實例:
DynamicMethod method = new DynamicMethod (DynamicMethodNameCounter.GetUniqueName (), typeof (void), new Type [] {typeof (IntPtr), typeof (object []) }, typeof (DynamicMethodNameCounter), true);
ILGenerator il = method.GetILGenerator ();
il.DeclareLocal (typeof (object));
il.Emit (OpCodes.Ldtoken, type);
il.Emit (OpCodes.Call, gettype);
il.Emit (OpCodes.Call, newobject);
il.Emit (OpCodes.Stloc_0);
il.Emit (OpCodes.Ldloc_0);
il.Emit (OpCodes.Ldarg_0);
il.Emit (OpCodes.Stfld, handle);
il.Emit (OpCodes.Ldloc_0);
var len = cinfo.GetParameters ().Length;
for (int i = 0; i < len; i++) {
il.Emit (OpCodes.Ldarg, 1);
il.Emit (OpCodes.Ldc_I4, i);
il.Emit (OpCodes.Ldelem_Ref);
}
il.Emit (OpCodes.Call, cinfo);
il.Emit (OpCodes.Ret);
return (Action<IntPtr, object?[]?>) method.CreateDelegate (typeof (Action <IntPtr, object []>));
調用返回的委託,使得IntPtr是Java.Lang.Object子類的句柄,而對象[]是該特定c#構造函數的任何參數。emit對於在啟動時第一次使用它以及以後的每次調用都有很大的成本。
經過仔細的審查,我們可以將handle字段設置為內部的,並將此代碼簡化為:
var newobj = RuntimeHelpers.GetUninitializedObject (cinfo.DeclaringType);
if (newobj is Java.Lang.Object o) {
o.handle = jobject;
} else if (newobj is Java.Lang.Throwable throwable) {
throwable.handle = jobject;
} else {
throw new InvalidOperationException ($"Unsupported type: '{newobj}'");
}
cinfo.Invoke (newobj, parms);
這段代碼所做的是在不調用構造函數的情況下創建一個對象,設置句柄字段,然後調用構造函數。這樣做是為了當c#構造函數開始時,Handle在任何Java.Lang.Object上都是有效的。構造函數內部的任何Java互操作(比如調用類上的其他Java方法)以及調用任何基本Java構造函數都需要Handle。
新代碼顯著改進了從Java調用的任何c#構造函數,因此這個特殊的更改改進的不僅僅是.NET MAUI。除了.NET 6之外,針對當前客户Xamarin. android的最新版本也附帶了這一更改。
查看xamarin-android#6766瞭解這個改進的詳細信息。
-
System.Reflection.Emit和構造函數:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#systemreflectionemit-and-constructors?ocid=AID3045631
-
xamarin-android#6766:
http://github.com/xamarin/xamarin-android/pull/6766
▌ System.Reflection.Emit和方法
當你在c#中重寫一個Java方法時,比如:
public class MainActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
//...
}
}
在從Java到c#的轉換過程中,我們必須封裝c#方法來處理異常,例如:
try
{
// Call the actual C# method here
}
catch (Exception e) when (_unhandled_exception (e))
{
androidEnvironment.UnhandledException (e);
if (Debugger.IsAttached || !JNIEnv.PropagateExceptions)
throw;
}
例如,如果在OnCreate()中未處理託管異常,那麼實際上會導致本機崩潰(並且沒有託管的c#堆棧跟蹤)。我們需要確保調試器在附加異常時能夠中斷,否則將記錄c#堆棧跟蹤。
從Xamarin開始,上面的代碼是通過System.Reflection.Emit生成的:
var dynamic = new DynamicMethod (DynamicMethodNameCounter.GetUniqueName (), ret_type, param_types, typeof (DynamicMethodNameCounter), true);
var ig = dynamic.GetILGenerator ();
LocalBuilder? retval = null;
if (ret_type != typeof (void))
retval = ig.DeclareLocal (ret_type);
ig.Emit (OpCodes.Call, wait_for_bridge_processing_method!);
var label = ig.BeginExceptionBlock ();
for (int i = 0; i < param_types.Length; i++)
ig.Emit (OpCodes.Ldarg, i);
ig.Emit (OpCodes.Call, dlg.Method);
if (retval != null)
ig.Emit (OpCodes.Stloc, retval);
ig.Emit (OpCodes.Leave, label);
bool filter = Debugger.IsAttached || !JNIEnv.PropagateExceptions;
if (filter && JNIEnv.mono_unhandled_exception_method != null) {
ig.BeginExceptFilterBlock ();
ig.Emit (OpCodes.Call, JNIEnv.mono_unhandled_exception_method);
ig.Emit (OpCodes.Ldc_I4_1);
ig.BeginCatchBlock (null!);
} else {
ig.BeginCatchBlock (typeof (Exception));
}
ig.Emit (OpCodes.Dup);
ig.Emit (OpCodes.Call, exception_handler_method!);
if (filter)
ig.Emit (OpCodes.Throw);
ig.EndExceptionBlock ();
if (retval != null)
ig.Emit (OpCodes.Ldloc, retval);
ig.Emit (OpCodes.Ret);
這段代碼被調用兩次為一個 dotnet new android 應用程序,但~58次為一個dotnet new maui應用程序!
我們意識到實際上可以為每個通用委託類型編寫一個強類型的“快速路徑”,而不是使用System.Reflection.Emit。有一個生成的委託匹配每個簽名:
void OnCreate(Bundle savedInstanceState);
// Maps to *JNIEnv, JavaClass, Bundle
// Internal to each assembly
internal delegate void _JniMarshal_PPL_V(IntPtr, IntPtr, IntPtr);
這樣我們就可以列出所有使用過的dotnet maui應用程序的簽名,比如:
class JNINativeWrapper
{
static Delegate? CreateBuiltInDelegate (Delegate dlg, Type delegateType)
{
switch (delegateType.Name)
{
// Unsafe.As<T>() is used, because _JniMarshal_PPL_V is generated internal in each assembly
case nameof (_JniMarshal_PPL_V):
return new _JniMarshal_PPL_V (Unsafe.As<_JniMarshal_PPL_V> (dlg).Wrap_JniMarshal_PPL_V);
// etc.
}
return null;
}
// Static extension method is generated to avoid capturing variables in anonymous methods
internal static void Wrap_JniMarshal_PPL_V (this _JniMarshal_PPL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0)
{
// ...
}
}
這種方法的缺點是,當使用新簽名時,我們必須列出更多的情況。我們不想詳盡地列出每一種組合,因為這會導致IL大小的增長。我們正在研究如何在未來的.NET版本中改進這一點。
查看xamarin-android#6657和xamarin-android#6707瞭解這個改進的詳細信息。
-
S ystem.Reflection.Emit和方法:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#systemreflectionemit-and-methods?ocid=AID3045631
-
xamarin-android#6657:
http://github.com/xamarin/xamarin-android/pull/6657
-
xamarin-android#6707:
http://github.com/xamarin/xamarin-android/pull/6707
▌ 更新的Java.Interop APIs
Java.Interop.dll中原始的Xamarin api是這樣的api:
-
JNIEnv.CallStaticObjectMethod
在Java中調用的“新方法”每次調用佔用的內存更少:
-
JniEnvironment.StaticMethods.CallStaticObjectMethod
當在構建時為Java方法生成c#綁定時,默認使用更新/更快的方法—在Xamarin.Android中已經有一段時間了。以前,Java綁定項目可以將$(AndroidCodegenTarget)設置為XAJavaInterop1,它在每次調用中緩存和重用jmethodID實例。請參閲java.interop文檔獲取關於該特性的歷史記錄。
其他有問題的地方是有“手動”綁定的地方。這些往往也是經常使用的方法,所以值得修復這些!
一些改善這種情況的例子:
-
JNIEnv.FindClass()在xamarin-android#6805
-
JavaList 和 JavaList<T>在 xamarin-android#6812
-
更新的Java.Interop APIs:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays?ocid=AID3045631
-
$(AndroidCodegenTarget):
http://docs.microsoft.com/en-us/xamarin/android/deploy-test/building-apps/build-properties#androidcodegentarget?ocid=AID3045631
-
java.interop:
http://github.com/xamarin/Java.Interop/commit/d9b43b52a2904e00b74b96c82a7c62c6a0c214ca
-
xamarin-android#6805:
http://github.com/xamarin/xamarin-android/pull/6805
-
xamarin-android#6812:
http://github.com/xamarin/xamarin-android/pull/6812
▌ 多維Java數組
當向Java來回傳遞c#數組時,中間步驟必須複製數組,以便適當的運行時能夠訪問它。這真的是一個開發者體驗的情況,因為c#開發者期望寫這樣的東西:
var array = new int[] { 1, 2, 3, 4};
MyJavaMethod (array);
在MyJavaMethod裏面會做:
IntPtr native_items = JNIEnv.NewArray (items);
try
{
// p/invoke here, actually calls into Java
}
finally
{
if (items != null)
{
JNIEnv.CopyArray (native_items, items); // If the calling method mutates the array
JNIEnv.DeleteLocalRef (native_items); // Delete our Java local reference
}
}
JNIEnv.NewArray()訪問一個“類型映射”,以知道需要將哪個Java類用於數組的元素。
dotnet new maui項目使用的特定android API有問題:
public ColorStateList (int[][]? states, int[]? colors)
發現一個多維 int[][] 數組可以訪問每個元素的“類型映射”。當啟用額外的日誌記錄時,我們可以看到這一點,許多實例:
monodroid: typemap: failed to map managed type to Java type: System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e (Module ID: 8e4cd939-3275-41c4-968d-d5a4376b35f5; Type token: 33554653)
monodroid-assembly: typemap: called from
monodroid-assembly: at android.Runtime.JNIEnv.TypemapManagedToJava(Type )
monodroid-assembly: at android.Runtime.JNIEnv.GetJniName(Type )
monodroid-assembly: at android.Runtime.JNIEnv.FindClass(Type )
monodroid-assembly: at android.Runtime.JNIEnv.NewArray(Array , Type )
monodroid-assembly: at android.Runtime.JNIEnv.NewArray[Int32[]](Int32[][] )
monodroid-assembly: at android.Content.Res.ColorStateList..ctor(Int32[][] , Int32[] )
monodroid-assembly: at Microsoft.Maui.Platform.ColorStateListExtensions.CreateButton(Int32 enabled, Int32 disabled, Int32 off, Int32 pressed)
對於這種情況,我們應該能夠調用JNIEnv.FindClass()一次,併為數組中的每一項重用這個值!
我們正在研究如何在未來的.NET版本中進一步改進這一點。一個這樣的例子是dotnet/maui#5654,在這裏我們只是簡單地考慮完全用Java來創建數組。
查看xamarin-android#6870瞭解這個改進的詳細信息。
-
多維Java數組:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays?ocid=AID3045631
-
dotnet/maui#5654:
http://github.com/dotnet/maui/pull/5654
-
xamarin-android#6870:
http://github.com/xamarin/xamarin-android/pull/6870
▌為android圖像使用Glide
Glide是現代android應用程序推薦的圖片加載庫。谷歌文檔甚至推薦使用它,因為內置的android Bitmap類可能很難正確使用。glidex.forms是在Xamarin.Forms中使用Glide的原型。但我們將 Glide 提升為未來在 .NET MAUI 中加載圖像的“方式”。
為了減少JNI互操作的開銷,.NET MAUI的Glide實現主要是用Java編寫的,例如:
import com.bumptech.glide.Glide;
//...
public static void loadImageFromUri(ImageView imageView, String uri, Boolean cachingEnabled, ImageLoaderCallback callback) {
//...
RequestBuilder<Drawable> builder = Glide
.with(imageView)
.load(androidUri);
loadInto(builder, imageView, cachingEnabled, callback);
}
ImageLoaderCallback在c#中子類化以處理託管代碼中的完成。其結果是,來自web的圖像的性能應該比以前在Xamarin.Forms中得到的性能有了顯著提高。
詳見dotnet/maui#759和dotnet/maui#5198。
-
為android圖像使用Glide:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays?ocid=AID3045631
-
Glide:
http://github.com/bumptech/glide
-
glidex.forms:
http://github.com/jonathanpeppers/glidex
-
dotnet/maui#759:
http://github.com/dotnet/maui/pull/759
-
dotnet/maui#5198:
http://github.com/dotnet/maui/pull/5198
▌減少Java互操作調用
假設你有以下Java api:
public void setFoo(int foo);
public void setBar(int bar);
這些方法的互操作如下:
public unsafe static void SetFoo(int foo)
{
JniArgumentValue* __args = stackalloc JniArgumentValue[1];
__args[0] = new JniArgumentValue(foo);
return _members.StaticMethods.InvokeInt32Method("setFoo.(I)V", __args);
}
public unsafe static void SetBar(int bar)
{
JniArgumentValue* __args = stackalloc JniArgumentValue[1];
__args[0] = new JniArgumentValue(bar);
return _members.StaticMethods.InvokeInt32Method("setBar.(I)V", __args);
}
所以調用這兩個方法會兩次調用stackalloc,兩次調用p/invoke。創建一個小型的Java包裝器會更有性能,例如:
public void setFooAndBar(int foo, int bar)
{
setFoo(foo);
setBar(bar);
}
翻譯為:
public unsafe static void SetFooAndBar(int foo, int bar)
{
JniArgumentValue* __args = stackalloc JniArgumentValue[2];
__args[0] = new JniArgumentValue(foo);
__args[1] = new JniArgumentValue(bar);
return _members.StaticMethods.InvokeInt32Method("setFooAndBar.(II)V", __args);
}
.NET MAUI視圖本質上是c#對象,有很多屬性需要在Java中以完全相同的方式設置。如果我們將這個概念應用到.NET MAUI中的每個android View中,我們可以創建一個~18參數的方法用於View創建。後續的屬性更改可以直接調用標準的android api。
對於非常簡單的.NET MAUI控件來説,這在性能上有了顯著的提高:
方法 |
平均 |
錯誤 |
標準差 |
0 代 |
已分配 |
Border(Before) |
323.2 µs |
0.82 µs |
0.68 µs |
0.9766 |
5KB |
Border(After) |
242.3 µs |
1.34 µs |
1.25 µs |
0.9766 |
5KB |
CollectionView(Before) |
354.6 µs |
2.61 µs |
2.31 µs |
1.4648 |
6KB |
CollectionView(After) |
258.3 µs |
0.49 µs |
0.43 µs |
1.4648 |
6KB |
請參閲dotnet/maui#3372瞭解有關此改進的詳細信息。
-
減少Java互操作調用:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#reduce-java-interop-calls?ocid=AID3045631
-
dotnet/maui#3372:
http://github.com/dotnet/maui/pull/3372
▌將android XML移植到Java
回顧android上的dotnet跟蹤輸出,我們可以看到合理的時間花費在:
20.32.ms mono.andorid!Andorid.Views.LayoutInflater.Inflate
回顧堆棧跟蹤,時間實際上花在了android/Java擴展布局上,而在.NET端沒有任何工作發生。
如果你看看編譯過的android .apk和res/layouts/bottomtablayout。在android Studio中,XML只是普通的XML。只有少數標識符被轉換為整數。這意味着android必須解析XML並通過Java的反射api創建Java對象——似乎我們不使用XML就可以獲得更快的性能?
通過標準的BenchmarkDotNet對比,我們發現在涉及互操作時,使用android佈局的表現甚至比使用c#更差:
方法 |
方法 |
錯誤 |
標準差 |
已分配 |
Java |
338.4 µs |
4.21 µs |
3.52 µs |
744 B |
CSharp |
410.2 µs |
7.92 µs |
6.61 µs |
1,336 B |
XML |
490.0 µs |
7.77 µs |
7.27 µs |
2,321 B |
接下來,我們將BenchmarkDotNet配置為單次運行,以更好地模擬啟動時發生的情況:
方法 |
中值 |
Java |
4.619 ms |
CSharp |
37.337 ms |
XML |
39.364 ms |
我們在.NET MAUI中看到了一個更簡單的佈局,底部標籤導航:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/bottomtab.navarea"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="fill"
android:layout_weight="1" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomtab.tabbar"
android:theme="@style/Widget.Design.BottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
我們可以將其移植到四個Java方法中,例如:
@NonNull
public static List<View> createBottomTabLayout(Context context, int navigationStyle);
@NonNull
public static LinearLayout createLinearLayout(Context context);
@NonNull
public static FrameLayout createFrameLayout(Context context, LinearLayout layout);
@NonNull
public static BottomNavigationView createNavigationBar(Context context, int navigationStyle, FrameLayout bottom)
這使得我們在android上創建底部標籤導航時只能從c#切換到Java 4次。它還允許android操作系統跳過加載和解析.xml來“膨脹”Java對象。我們在dotnet/maui中執行了這個想法,在啟動時刪除所有LayoutInflater.Inflate()調用。
請參閲dotnet/maui#5424, dotnet/maui#5493,和dotnet/maui#5528瞭解這些改進的詳細信息。
-
將android XML移植到Java:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#port-android-xml-to-java?ocid=AID3045631
-
dotnet/maui#5424:
http://github.com/dotnet/maui/pull/5424
-
dotnet/maui#5493:
http://github.com/dotnet/maui/pull/5493
-
dotnet/maui#5528:
http://github.com/dotnet/maui/pull/5528
▌刪除Microsoft.Extensions.Hosting
hosting提供了一個.NET通用主機,用於在.NET應用程序中管理依賴注入、日誌記錄、配置和應用生命週期。這對啟動時間有影響,似乎不適合移動應用程序。
從.NET MAUI中移除Microsoft.Extensions.Hosting使用是有意義的。. net MAUI沒有試圖與“通用主機”互操作來構建DI容器,而是有自己的簡單實現,它針對移動啟動進行了優化。此外,. net MAUI默認不再添加日誌記錄提供程序。
通過這一改變,我們看到dotnet new maui android應用程序的啟動時間減少了5-10%。在iOS上,它減少了相同應用程序的大小,從19.2 MB => 18.0 MB。
詳見dotnet/maui#4505和dotnet/maui#4545。
-
刪除Microsoft.Extensions.Hosting:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-microsoftextensionshosting?ocid=AID3045631
-
.NET通用主機:
http://docs.microsoft.com/en-us/dotnet/core/extensions/generic-host?ocid=AID3045631
-
dotnet/maui#4505:
http://github.com/dotnet/maui/pull/4505
-
dotnet/maui#4545:
http://github.com/dotnet/maui/pull/4545
▌在啟動時減少Shell初始化
Xamarin. Forms Shell是跨平台應用程序導航的一種模式。這個模式是在.NET MAUI中提出的,它被推薦作為構建應用程序的默認方式。
當我們發現在啟動時使用Shell的成本(對於Xamarin和Xamarin.form和.NET MAUI),我們找到了幾個可以優化的地方:
-
不要在啟動時解析路由——要等到一個需要它們的導航發生。
-
如果沒有為導航提供查詢字符串,則只需跳過處理查詢字符串的代碼。這將刪除過度使用System.Reflection的代碼路徑。
-
如果頁面沒有可見的BottomNavigationView,那麼不要設置菜單項或任何外觀元素。
請參閲dotnet/maui#5262瞭解此改進的詳細信息。
-
在啟動時減少Shell初始化:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#less-shell-initialization-on-startup?ocid=AID3045631
-
Xamarin. Forms Shell:
http://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/?ocid=AID3045631
-
dotnet/maui#5262:
http://github.com/dotnet/maui/pull/5262
▌字體不應該使用臨時文件
大量的時間花在.NET MAUI應用程序加載字體上:
32.19ms Microsoft.Maui!Microsoft.Maui.FontManager.CreateTypeface(System.ValueTuple`3<string, Microsoft.Maui.FontWeight, bool>)
檢查代碼時,它所做的工作比需要的更多:
1.將androidAsset文件保存到臨時文件夾。
2.使用android API, Typeface.CreateFromFile()來加載文件。
我們實際上可以直接使用Typeface.CreateFromAsset() android API,根本不用臨時文件。
請參閲dotnet/maui#4933瞭解有關此改進的詳細信息。
-
字體不應該使用臨時文件:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#fonts-should-not-use-temporary-files?ocid=AID3045631
-
dotnet/maui#4933:
http://github.com/dotnet/maui/pull/4933
▌編譯時在平台上計算
{OnPlatform}標記擴展的使用:
<Label Text="Platform: " />
<Label Text="{OnPlatform Default=Unknown, android=android, iOS=iOS" />
…實際上可以在編譯時計算,net6.0-android和net6.0-ios會得到適當的值。在未來的.NET版本中,我們將對 XML元素進行同樣的優化。
詳見dotnet/maui#4829和dotnet/maui#5611。
-
編譯時在平台上計算:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#compute-onplatform-at-compile-time?ocid=AID3045631
-
dotnet/maui#4829:
http://github.com/dotnet/maui/pull/4829
-
dotnet/maui#5611:
http://github.com/dotnet/maui/pull/5611
▌在XAML中使用編譯轉換器
以下類型現在在XAML編譯時轉換,而不是在運行時:
-
顏色:dotnet /maui# 4687
-
角半徑: dotnet / maui # 5192
-
字形大小:dotnet / maui # 5338
-
網格長度, 行定義, 列定義: dotnet/maui#5489
這導致從.xaml文件生成更好/更快的IL。
-
在XAML中使用編譯轉換器:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#use-compiled-converters-in-xaml?ocid=AID3045631
-
dotnet /maui# 4687:
http://github.com/dotnet/maui/pull/4687
-
dotnet / maui # 5192:
http://github.com/dotnet/maui/pull/5192
-
dotnet / maui # 5338:
http://github.com/dotnet/maui/pull/5338
-
dotnet/maui#5489:
http://github.com/dotnet/maui/pull/5489
▌優化顏色解析
Microsoft.Maui.Graphics.Color.Parse()的原始代碼可以重寫,以更好地使用Span並避免字符串分配。
方法 |
平均 |
錯誤 |
標準差 |
0 代 |
已分配 |
Parse (之前) |
99.13 ns |
0.281 ns |
0.235 ns |
0.0267 |
168 B |
Parse (之後) |
52.54 ns |
0.292 ns |
0.259 ns |
0.0051 |
32 B |
能夠在ReadonlySpan<char> dotnet/csharplang#1881上使用switch語句,將在未來的.NET版本中進一步改善這種情況。
看到dotnet / Microsoft.Maui.Graphics # 343和dotnet / Microsoft.Maui.Graphics # 345關於這個改進的細節。
-
優化顏色解析:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#optimize-color-parsing?ocid=AID3045631
-
dotnet/csharplang#1881:
http://github.com/dotnet/csharplang/issues/1881
-
dotnet / Microsoft.Maui.Graphics # 343:
http://github.com/dotnet/Microsoft.Maui.Graphics/pull/343
-
dotnet / Microsoft.Maui.Graphics # 345:
http://github.com/dotnet/Microsoft.Maui.Graphics/pull/345
▌不要使用區域性識別的字符串比較
回顧一個新的naui項目的dotnet跟蹤輸出,可以看到android上第一個區域性感知字符串比較的真實成本:
6.32ms Microsoft.Maui.Controls!Microsoft.Maui.Controls.ShellNavigationManager.GetNavigationState
3.82ms Microsoft.Maui.Controls!Microsoft.Maui.Controls.ShellUriHandler.FormatUri
3.82ms System.Private.CoreLib!System.String.StartsWith
2.57ms System.Private.CoreLib!System.Globalization.CultureInfo.get_CurrentCulture
實際上,我們甚至不希望在本例中使用區域性比較—它只是從Xamarin.Forms引入的代碼。
例如,如果你有:
if (text.StartsWith("f"))
{
// do something
}
在這種情況下,你可以簡單地這樣做:
if (text.StartsWith("f", StringComparision.Ordinal))
{
// do something
}
如果在整個應用程序中執行,System.Globalization.CultureInfo.CurrentCulture可以避免被調用,並且可以稍微提高If語句的總體速度。
為了解決整個dotnet/maui回購的這種情況,我們引入了代碼分析規則來捕捉這些:
dotnet_diagnostic.CA1307.severity = error
dotnet_diagnostic.CA1309.severity = error
請參閲dotnet/maui#4988瞭解有關改進的詳細信息。
-
不要使用區域性識別的字符串比較:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#dont-use-culture-aware-string-comparisons?ocid=AID3045631
-
dotnet/maui#4988:
http://github.com/dotnet/maui/pull/4988
▌懶惰地創建日誌
ConfigureFonts() API在啟動時花費了一些時間來做一些可以延遲到以後的工作。我們還可以改進Microsoft.Extensions中日誌基礎設施的一般用法。
我們所做的一些改進如下:
-
推遲創建“記錄器”類,直到需要它們時再創建。
-
內置的日誌記錄基礎設施在默認情況下是禁用的,必須顯式啟用。
-
延遲調用android的EmbeddedFontLoader中的Path.GetTempPath(),直到需要它。
-
不要使用ILoggerFactory創建通用記錄器。而是直接獲取ILogger服務,這樣它就被緩存了。
請參閲dotnet/maui#5103瞭解有關此改進的詳細信息。
-
懶惰地創建日誌:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#create-loggers-lazily?ocid=AID3045631
-
dotnet/maui#5103:
http://github.com/dotnet/maui/pull/5103
▌使用工廠方法進行依賴注入
當使用Microsoft.Extensions。DependencyInjection,註冊服務,比如:
IServiceCollection services /* ... */;
services.TryAddSingleton<IFooService, FooService>();
Microsoft.Extensions必須做一些System.Reflection來創建FooService的第一個實例。這是值得注意的dotnet跟蹤輸出在android上。
相反,如果你這樣做了:
// If FooService has no dependencies
services.TryAddSingleton<IFooService>(sp => new FooService());
// Or if you need to retrieve some dependencies
services.TryAddSingleton<IFooService>(sp => new FooService(sp.GetService<IBar>()));
在這種情況下,Microsoft.Extensions可以簡單地調用lamdba/匿名方法,而不需要系統。反射。
我們在所有的dotnet/maui上進行了改進,並使用了bannedapianalyzer,這樣就不會有人意外地使用TryAddSingleton()更慢的重載。
請參閲dotnet/maui#5290瞭解有關此改進的詳細信息。
-
使用工廠方法進行依賴注入:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#use-factory-methods-for-dependency-injection?ocid=AID3045631
-
b annedapianalyzer:
http://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md
-
dotnet/maui#5290:
http://github.com/dotnet/maui/pull/5290
▌懶惰地負載ConfigurationManager
configurationmanager並沒有被許多移動應用程序使用,而且創建一個是非常昂貴的!(例如,在android上約為7.59ms)
在.NET MAUI中,一個ConfigurationManager在啟動時默認被創建,我們可以使用Lazy延遲它的創建,所以它將不會被創建,除非請求。
請參閲dotnet/maui#5348瞭解有關此改進的詳細信息。
-
懶惰地負載ConfigurationManager:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#load-configurationmanager-lazily?ocid=AID3045631
-
dotnet/maui#5348:
http://github.com/dotnet/maui/pull/5348
▌默認VerifyDependencyInjectionOpenGenericServiceTrimmability
.NET Podcast樣本花費了4-7ms的時間:
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallsiteFactory.ValidateTrimmingAnnotations()
MSBuild屬性$(verifydependencyinjectionopengenericservicetrimability)觸發該方法運行。這個特性開關確保dynamallyaccessedmembers被正確地應用於打開依賴注入中的泛型類型。
在基礎.NET SDK中,當publishtrim =true時,該開關將被啟用。然而,android應用程序在Debug版本中並沒有設置publishtrim =true,所以開發者錯過了這個驗證。
相反,在已發佈的應用程序中,我們不想支付這種驗證的成本。所以這個特性開關應該在Release版本中關閉。
查看xamarin-android#6727和xamarin-macios#14130瞭解關於這個改進的詳細信息。
-
默認VerifyDependencyInjectionOpenGenericServiceTrimmability:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#default-verifydependencyinjectionopengenericservicetrimmability?ocid=AID3045631
-
.NET Podcast:
http://github.com/microsoft/dotnet-podcasts
-
xamarin-android#6727:
http://github.com/xamarin/xamarin-android/pull/6727
-
xamarin-macios#14130:
http://github.com/xamarin/xamarin-macios/pull/14130
▌改進內置AOT配置文件
Mono運行時有一個關於每個方法的JIT時間的報告(參見我們的文檔),例如:
Total(ms) | Self(ms) | Method
3.51 | 3.51 | Microsoft.Maui.Layouts.GridLayoutManager/GridStructure:.ctor (Microsoft.Maui.IGridLayout,double,double)
1.88 | 1.88 | Microsoft.Maui.Controls.Xaml.AppThemeBindingExtension/<>c__DisplayClass20_0:<Microsoft.Maui.Controls.Xaml.IMarkupExtension<Microsoft.Maui.Controls.BindingBase>.ProvideValue>g__minforetriever|0 ()
1.66 | 1.66 | Microsoft.Maui.Controls.Xaml.OnIdiomExtension/<>c__DisplayClass32_0:<ProvideValue>g__minforetriever|0 ()
1.54 | 1.54 | Microsoft.Maui.Converters.ThicknessTypeConverter:ConvertFrom (System.ComponentModel.ITypeDescriptorContext,System.Globalization.CultureInfo,object)
這是一個使用Profiled AOT的版本構建中.NET Podcast示例中的頂級jit時間選擇。這些似乎是開發人員希望在. net MAUI應用程序中使用的常用api。
為了確保這些方法在AOT配置文件中,我們在dotnet/maui中使用了這些api
_=new Microsoft.Maui.Layouts.GridLayoutManager(new Grid()).Measure(100, 100);
<SolidColorBrush x:Key="ProfiledAot_AppThemeBinding_Color" Color="{AppThemeBinding Default=Black}"/>
<CollectionView x:Key="ProfiledAot_CollectionView_OnIdiom_Thickness" Margin="{OnIdiom Default=1,1,1,1}" />
在這個測試應用程序中調用這些方法可以確保它們位於內置的. net MAUI AOT配置文件中。
在這個更改之後,我們看了一個更新的JIT報告:
Total (ms) | Self (ms) | Method
2.61 | 2.61 | string:SplitInternal (string,string[],int,System.StringSplitOptions)
1.57 | 1.57 | System.Number:NumberToString (System.Text.ValueStringBuilder&,System.Number/NumberBuffer&,char,int,System.Globalization.NumberFormatInfo)
1.52 | 1.52 | System.Number:TryParseInt32IntegerStyle (System.ReadOnlySpan`1<char>,System.Globalization.NumberStyles,System.Globalization.NumberFormatInfo,int&)
這導致了進一步的補充:
var split = "foo;bar".Split(';');
var x = int.Parse("999");
x.ToString();
我們對Color.Parse()、Connectivity做了類似的修改.NETworkAccess DeviceInfo。成語,AppInfo。.NET MAUI應用程序中應該經常使用的requestdtheme。
請參閲dotnet/maui#5559, dotnet/maui#5682,和dotnet/maui#6834瞭解這些改進的詳細信息。
如果你想在.NET 6中記錄一個自定義的AOT配置文件,你可以嘗試我們的實驗包Mono.Profiler.Android。我們正在努力在未來的.NET版本中完全支持記錄自定義概要文件。
-
改進內置AOT配置文件:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#improve-the-built-in-aot-profile?ocid=AID3045631
-
參見我們的文檔:
http://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/profiling.md#profiling-the-jit-compiler
-
.NET Podcast:
http://github.com/microsoft/dotnet-podcasts
-
dotnet/maui#5559:
http://github.com/dotnet/maui/pull/5559
-
dotnet/maui#5682:
http://github.com/dotnet/maui/pull/5682
-
dotnet/maui#6834:
http://github.com/dotnet/maui/pull/6834
-
Mono.Profiler.Android:
http://github.com/jonathanpeppers/Mono.Profiler.Android
▌啟用AOT圖像的延遲加載
以前,Mono運行時將在啟動時加載所有AOT圖像,以驗證託管.NET程序集(例如Foo.dll)的MVID是否與AOT圖像(libFoo.dll.so)匹配。在大多數.NET應用程序中,一些AOT映像可能稍後才需要加載。
Mono中引入了一個新的——aot-lazy-assembly-load或mono_opt_aot_lazy_assembly_load設置,android工作負載可以選擇。我們發現這將dotnet new maui項目在Pixel 6 Pro上的啟動時間提高了約25ms。
這是默認啟用的,但如果需要,你可以在你的。csproj中通過以下方式禁用此設置:
<AndroidAotEnableLazyLoad>false</AndroidAotEnableLazyLoad>
查看dotnet/runtime#67024和xamarin-android #6940瞭解這些改進的詳細信息。
-
啟用AOT圖像的延遲加載:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#enable-lazy-loading-of-aot-images?ocid=AID3045631
-
dotnet/runtime#67024:
http://github.com/dotnet/runtime/pull/67024
-
xamarin-android #6940:
http://github.com/xamarin/xamarin-android/pull/6940
▌刪除System.Uri中未使用的編碼對象
一個MAUI應用程序的dotnet跟蹤輸出,顯示大約7ms花費了加載UTF32和Latin1編碼的第一次系統。使用Uri api:
namespace System
{
internal static class UriHelper
{
internal static readonly Encoding s_noFallbackCharUTF8 = Encoding.GetEncoding(
Encoding.UTF8.CodePage, new EncoderReplacementFallback(""), new DecoderReplacementFallback(""));
這個字段是不小心留在原地的。只需刪除s_noFallbackCharUTF8字段,就可以改進任何使用System.Uri 或相關的api的. net應用程序的啟動。
參見dotnet/runtime#65326瞭解有關此改進的詳細信息。
-
刪除System.Uri中未使用的編碼對象:
http://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-unused-encoding-object-in-systemuri?ocid=AID3045631
-
dotnet/runtime#65326:
http://github.com/dotnet/runtime/pull/65326
明天我們將介紹應用程序大小的改進、.NET Podcast示例中的改進、實驗性或高級選項,更多改進請見下期內容!
謝謝你讀完了本文!歡迎在 評論區留言 分享你的想法,並且 轉發到朋友圈 。
如果你對本文青睞有加,想要轉載到自己的平台, 請在後台回覆「轉載」 與我們取得聯繫!
長按識別二維碼
關注微軟中國MSDN
點擊「閲讀原文」獲取學習資源 ~
- 宣佈 .NET 7 預覽版 6
- 使用 LSM Tree 思想實現一個 KV 數據庫
- Optoma的雲上“千里眼”
- 用 Uno Platform 構建一個 Kanban-style Todo App
- 請查收.NET MAUI 的最新學習資源
- 6月更新 | Visual Studio Code Python
- .NET MAUI in Mac
- .NET MAUI 性能提升(上)
- 官宣.NET 7 預覽版5
- NCF的Dapr應用實例的運行
- 5月更新丨VS Code Python
- 如果你還沒聽過長城汽車的新故事......
- Kubernetes 集羣和應用監控方案的設計與實踐(上)
- 由世紀互聯運營的 Power Apps Portals 正式在華商用
- 在嗎?看看MAUI候選版本3!
- 官宣 .NET 7 預覽版3
- 疫情影響企業發展能力?也許它能幫您擺脱困境
- 做⼀個可以聊天的 VS Code 插件
- .NET MAUI候選版本,能帶給你什麼小驚喜?
- 加速Spring現代化,我們做了什麼?