.NET MAUI 性能提升(上)

語言: CN / TW / HK

本文閲讀時間: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:

    https://github.com/microsoft/dotnet-podcasts

  • maui-profiling:

    https://github.com/jonathanpeppers/maui-profiling

  • Shell:

    https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/?ocid=AID3045631

  • .NET Podcast App (Shell):

    https://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。你可以在https://speedscope.app上打開這個文件,深入瞭解每個方法在應用程序啟動期間所花費的時間:

在android應用程序中使用dotnet跟蹤的更多細節,請參閲我們的文檔。我建議在android設備上分析Release版本,以獲得應用在現實世界中的最佳表現。

  • 在移動設備上進行分析:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#profiling-on-mobile?ocid=AID3045631

  • dotnet-dsrouter:

    https://docs.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dsrouter?ocid=AID3045631

  • 我們的文檔:

    https://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瞭解這些改進的詳細信息。

  • 測量隨着時間的推移:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#measuring-over-time?ocid=AID3045631

  • .NET Podcas:

    https://github.com/microsoft/dotnet-podcasts

  • dotnet-podcasts#58:

    https://github.com/microsoft/dotnet-podcasts/pull/58

  • Android x# 520:

    https://github.com/xamarin/AndroidX/pull/520

  • dotnet/maui#6419:

    https://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:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#profiled-aot?ocid=AID3045631

  • 啟動跟蹤或分析AOT:

    https://devblogs.microsoft.com/xamarin/faster-startup-times-with-startup-tracing-on-android/?ocid=AID3045631

  • Mono.Profiler. Android:

    https://github.com/jonathanpeppers/Mono.Profiler.Android

  • xamarin-Android#6547:

    https://github.com/xamarin/xamarin-android/pull/6547

  • dotnet/maui#4859:

    https://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瞭解關於這個改進的詳細信息。

  • 單文件程序集存儲器:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#single-file-assembly-stores

  • mmap系統調用:

    https://man7.org/linux/man-pages/man2/mmap.2.html

  • mmap:

    https://man7.org/linux/man-pages/man2/mmap.2.html

  • 程序集存儲讀取器:

    https://github.com/xamarin/xamarin-android/tree/main/tools/assembly-store-reader

  • ILSpy:

    https://github.com/icsharpcode/ILSpy

  • xamarin-android#6311:

    https://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:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#spanify-registernativemembers?ocid=AID3045631

  • Java本機接口(JNI):

    https://en.wikipedia.org/wiki/Java_Native_Interface

  • Span:

    https://docs.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay?ocid=AID3045631

  • xamarin-android#6708:

    https://github.com/xamarin/xamarin-android/pull/6708https://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和構造函數:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#systemreflectionemit-and-constructors?ocid=AID3045631

  • xamarin-android#6766:

    https://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和方法:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#systemreflectionemit-and-methods?ocid=AID3045631

  • xamarin-android#6657:

    https://github.com/xamarin/xamarin-android/pull/6657

  • xamarin-android#6707:

    https://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:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays?ocid=AID3045631

  • $(AndroidCodegenTarget):

    https://docs.microsoft.com/en-us/xamarin/android/deploy-test/building-apps/build-properties#androidcodegentarget?ocid=AID3045631

  • java.interop:

    https://github.com/xamarin/Java.Interop/commit/d9b43b52a2904e00b74b96c82a7c62c6a0c214ca

  • xamarin-android#6805:

    https://github.com/xamarin/xamarin-android/pull/6805

  • xamarin-android#6812:

    https://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數組:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays?ocid=AID3045631

  • dotnet/maui#5654:

    https://github.com/dotnet/maui/pull/5654

  • xamarin-android#6870:

    https://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:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#multi-dimensional-java-arrays?ocid=AID3045631

  • Glide:

    https://github.com/bumptech/glide

  • glidex.forms:

    https://github.com/jonathanpeppers/glidex

  • dotnet/maui#759:

    https://github.com/dotnet/maui/pull/759

  • dotnet/maui#5198:

    https://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互操作調用:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#reduce-java-interop-calls?ocid=AID3045631

  • dotnet/maui#3372:

    https://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:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#port-android-xml-to-java?ocid=AID3045631

  • dotnet/maui#5424:

    https://github.com/dotnet/maui/pull/5424

  • dotnet/maui#5493:

    https://github.com/dotnet/maui/pull/5493

  • dotnet/maui#5528:

    https://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:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-microsoftextensionshosting?ocid=AID3045631

  • .NET通用主機:

    https://docs.microsoft.com/en-us/dotnet/core/extensions/generic-host?ocid=AID3045631

  • dotnet/maui#4505:

    https://github.com/dotnet/maui/pull/4505

  • dotnet/maui#4545:

    https://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初始化:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#less-shell-initialization-on-startup?ocid=AID3045631

  • Xamarin. Forms Shell:

    https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/?ocid=AID3045631

  • dotnet/maui#5262:

    https://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瞭解有關此改進的詳細信息。

  • 字體不應該使用臨時文件:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#fonts-should-not-use-temporary-files?ocid=AID3045631

  • dotnet/maui#4933:

    https://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。

  • 編譯時在平台上計算:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#compute-onplatform-at-compile-time?ocid=AID3045631

  • dotnet/maui#4829:

    https://github.com/dotnet/maui/pull/4829

  • dotnet/maui#5611:

    https://github.com/dotnet/maui/pull/5611

▌在XAML中使用編譯轉換器

以下類型現在在XAML編譯時轉換,而不是在運行時:

  • 顏色:dotnet /maui# 4687

  • 角半徑: dotnet / maui # 5192

  • 字形大小:dotnet / maui # 5338

  • 網格長度, 行定義, 列定義: dotnet/maui#5489

這導致從.xaml文件生成更好/更快的IL。

  • 在XAML中使用編譯轉換器:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#use-compiled-converters-in-xaml?ocid=AID3045631

  • dotnet /maui# 4687:

    https://github.com/dotnet/maui/pull/4687

  • dotnet / maui # 5192:

    https://github.com/dotnet/maui/pull/5192

  • dotnet / maui # 5338:

    https://github.com/dotnet/maui/pull/5338

  • dotnet/maui#5489:

    https://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關於這個改進的細節。

  • 優化顏色解析:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#optimize-color-parsing?ocid=AID3045631

  • dotnet/csharplang#1881:

    https://github.com/dotnet/csharplang/issues/1881

  • dotnet / Microsoft.Maui.Graphics # 343:

    https://github.com/dotnet/Microsoft.Maui.Graphics/pull/343

  • dotnet / Microsoft.Maui.Graphics # 345:

    https://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瞭解有關改進的詳細信息。

  • 不要使用區域性識別的字符串比較:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#dont-use-culture-aware-string-comparisons?ocid=AID3045631

  • dotnet/maui#4988:

    https://github.com/dotnet/maui/pull/4988

▌懶惰地創建日誌

ConfigureFonts() API在啟動時花費了一些時間來做一些可以延遲到以後的工作。我們還可以改進Microsoft.Extensions中日誌基礎設施的一般用法。

我們所做的一些改進如下:

  • 推遲創建“記錄器”類,直到需要它們時再創建。

  • 內置的日誌記錄基礎設施在默認情況下是禁用的,必須顯式啟用。

  • 延遲調用android的EmbeddedFontLoader中的Path.GetTempPath(),直到需要它。

  • 不要使用ILoggerFactory創建通用記錄器。而是直接獲取ILogger服務,這樣它就被緩存了。

請參閲dotnet/maui#5103瞭解有關此改進的詳細信息。

  • 懶惰地創建日誌:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#create-loggers-lazily?ocid=AID3045631

  • dotnet/maui#5103:

    https://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瞭解有關此改進的詳細信息。

  • 使用工廠方法進行依賴注入:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#use-factory-methods-for-dependency-injection?ocid=AID3045631

  • b annedapianalyzer:

    https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md

  • dotnet/maui#5290:

    https://github.com/dotnet/maui/pull/5290

▌懶惰地負載ConfigurationManager

configurationmanager並沒有被許多移動應用程序使用,而且創建一個是非常昂貴的!(例如,在android上約為7.59ms)

在.NET MAUI中,一個ConfigurationManager在啟動時默認被創建,我們可以使用Lazy延遲它的創建,所以它將不會被創建,除非請求。

請參閲dotnet/maui#5348瞭解有關此改進的詳細信息。

  • 懶惰地負載ConfigurationManager:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#load-configurationmanager-lazily?ocid=AID3045631

  • dotnet/maui#5348:

    https://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:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#default-verifydependencyinjectionopengenericservicetrimmability?ocid=AID3045631

  • .NET Podcast:

    https://github.com/microsoft/dotnet-podcasts

  • xamarin-android#6727:

    https://github.com/xamarin/xamarin-android/pull/6727

  • xamarin-macios#14130:

    https://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配置文件:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#improve-the-built-in-aot-profile?ocid=AID3045631

  • 參見我們的文檔:

    https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/profiling.md#profiling-the-jit-compiler

  • .NET Podcast:

    https://github.com/microsoft/dotnet-podcasts

  • dotnet/maui#5559:

    https://github.com/dotnet/maui/pull/5559

  • dotnet/maui#5682:

    https://github.com/dotnet/maui/pull/5682

  • dotnet/maui#6834:

    https://github.com/dotnet/maui/pull/6834

  • Mono.Profiler.Android:

    https://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圖像的延遲加載:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#enable-lazy-loading-of-aot-images?ocid=AID3045631

  • dotnet/runtime#67024:

    https://github.com/dotnet/runtime/pull/67024

  • xamarin-android #6940:

    https://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中未使用的編碼對象:

    https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-unused-encoding-object-in-systemuri?ocid=AID3045631

  • dotnet/runtime#65326:

    https://github.com/dotnet/runtime/pull/65326

明天我們將介紹應用程序大小的改進、.NET Podcast示例中的改進、實驗性或高級選項,更多改進請見下期內容!

謝謝你讀完了本文!歡迎在 評論區留言 分享你的想法,並且 轉發到朋友圈

如果你對本文青睞有加,想要轉載到自己的平台, 請在後台回覆「轉載」 與我們取得聯繫!

長按識別二維碼

關注微軟中國MSDN

點擊「閲讀原文」獲取學習資源 ~