Улучшение производительности .NET MAUI (часть 1)
Улучшение производительности .NET MAUI (часть 1)

(Время чтения статьи: 20 минут)

Пользовательский интерфейс многоплатформенного приложения .NET (MAUI) объединяет API-интерфейсы Android, iOS, macOS и Windows в единый API, поэтому вы можете написать приложение, которое будет работать на многих платформах. Мы фокусируемся на повышении вашей повседневной продуктивности и производительности ваших приложений. Мы считаем, что повышение продуктивности разработчиков не должно происходить за счет производительности приложений.

То же самое касается размера приложения: какие накладные расходы возникают в пустом приложении .NET MAUI. Когда мы начали оптимизировать .NET MAUI, стало ясно, что iOS необходимо проделать некоторую работу по увеличению размера приложения, в то время как Android не хватает производительности при загрузке.

Приложение iOS для нового проекта Maui в DotNet изначально будет иметь размер около 18 МБ. Аналогично, время запуска .NET MAUI на Android в предыдущих предварительных версиях не было идеальным:

приложение

рамка

Время запуска (мс)

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

Это в среднем 10 запусков на устройстве Pixel 5. См. нашу документацию по профилированию maui, чтобы узнать, как получить эти числа.

Наша цель — сделать .NET MAUI быстрее, чем его предшественник Xamarin. Понятно, что нам также есть над чем поработать и над самим .NET MAUI. Новые шаблоны dotnet для Android выпускаются быстрее, чем Xamarin.Android, главным образом из-за новой среды выполнения BCL и Mono в .NET 6.

Новые шаблоны .NET maui пока не используют режим навигации Shell, но планируется сделать его режимом навигации по умолчанию для .NET maui. Когда мы приняли это изменение, мы знали, что шаблоны повлияют на производительность.

Чтобы достичь того, что мы имеем сегодня, потребовалось сотрудничество нескольких разных команд. Мы улучшили Microsoft.Extensions, использование внедрения зависимостей, компиляцию AOT, взаимодействие с Java, XAML, код .NET MAUI и многое другое.

приложение

рамка

Время запуска (мс)

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 без использования оболочки.

Контент очень богатый, проверьте его, чтобы узнать, есть ли какие-либо обновления, которых вы с нетерпением ждете!

  • .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иметод
  • Обновленные API Java.Interop.
  • Многомерный массив Java
  • Использование Glide для изображений Android
  • Уменьшите количество вызовов взаимодействия Java
  • будет андроид XML в порт Java
  • Удалить Microsoft.Extensions.Hosting
  • Уменьшить инициализацию оболочки при запуске
  • Шрифты не должны использовать временные файлы.
  • Вычисляется на платформе во время компиляции
  • Использование преобразователей компилятора в XAML
  • Оптимизировать анализ цвета
  • Не используйте сравнения строк с учетом языка и региональных параметров.
  • Лениво создавать журнал
  • Внедрение зависимостей с использованием фабричного метода
  • Ленивая загрузка ConfigurationManager
  • по умолчаниюVerifyDependencyInjectionOpenGenericServiceTrimmability
  • Улучшение встроенного файла конфигурации AOT.
  • Включить отложенную загрузку изображений AOT
  • Удалите неиспользуемые объекты кодировки в System.Uri.

Улучшение производительности при запуске

▌Выполнение анализа на мобильных устройствах

Я должен упомянуть инструменты диагностики .NET, доступные на мобильных платформах, потому что это наш шаг 0 в ускорении работы .NET MAUI.

Анализ .NET 6 Приложение Android требует использования инструмента под названием dotnet-dsrouter. Этот инструмент позволяет отслеживать соединение Dotnet с работающим мобильным приложением в Android, iOS и т. д. Вероятно, это то, что мы используем. Анализ .NET Самый эффективный инструмент MAUI.

Чтобы начать использовать dotnettrace и dsrouter, сначала настройте некоторые параметры через adb и запустите dsrouter:

Язык кода:javascript
копировать
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, например:

Язык кода:javascript
копировать
dotnet-trace collect --diagnostic-port /tmp/maui-app --format speedscope

При запуске программы используйте -c Releaseи-p:androidEnableProfiler=true После сборки приложения Android, когда dotnet Когда трассировка выводится, вы заметите соединение:

Язык кода:javascript
копировать
Press <Enter> or <Ctrl+C> to exit...812  (KB)

После того, как ваше приложение полностью запустится,Просто нажмите клавишу ввода, чтобы сохранить *.speedscope в текущем каталоге. Вы можете открыть этот файл по адресу https://speedscope.app.,Узнайте подробнее, сколько времени потратил каждый метод во время запуска приложения:

Более подробная информация об использовании отслеживания dotnet в приложении для Android,Пожалуйста, ознакомьтесь с нашей документацией. Рекомендую проанализировать версию Release на устройстве Android,Чтобы получить максимальную производительность приложения в реальном мире.

  • Анализируйте на мобильных устройствах: 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 Foundation создали конвейер для отслеживания сценариев производительности .NET MAUI, таких как:

  • Размер упаковки
  • Размер диска (без сжатия)
  • Классификация одного файла
  • приложениезапускать

Это позволяет нам видеть влияние улучшений или регрессов с течением времени, просматривая цифры для каждой фиксации в репозитории dotnet/maui. Мы также можем определить, вызвана ли эта разница изменениями в xamarin-android, xamarin-macios или dotnet/runtime.

Например, график времени запуска (в миллисекундах) для нового шаблона dotnet maui, работающего на физическом устройстве Pixel 4a:

Обратите внимание, что Pixel 4a намного медленнее Pixel 5.

Мы можем выявить регрессы и улучшения, произошедшие в dotnet/maui. Это очень полезно для отслеживания наших целей.

Аналогично, мы можем видеть прогресс приложения .NET Podcast с течением времени на том же устройстве Pixel 4a:

На этой диаграмме мы действительно уделяем особое внимание, потому что это «настоящее приложение» и оно близко к тому, что разработчики видят в своих собственных мобильных приложениях.

Что касается размера приложения,Это более стабильное число – когда дела идут хуже или лучше.,Обнулить легко:

Подробные сведения об этих улучшениях см. в 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

▌Чужой АОТ

В наших первоначальных тестах производительности .NET MAUI мы видели, как работает код, скомпилированный JIT (точно в срок) и AOT (заранее):

приложение

Время JIT (мс)

Время АОТ (мс)

DotNet Нью-Мауи

1078.0ms

683.9ms

JIT-обработка происходит каждый раз при вызове метода C#, что неявно влияет на производительность запуска мобильного приложения.

Другая проблема — увеличение размера приложения из-за AOT. Каждая сборка .NET добавляет в последнее приложение собственную библиотеку Android. Чтобы лучше использовать оба мира,Запуск отслеживания или профилирования AOT — это текущая функция Xamarin.Android. Это механизм пути запуска приложения AOT.,Это значительно сокращает время запуска,И лишь скромное увеличение в размерах.

В .NET В версии 6 это опция по умолчанию, которая имеет смысл. Раньше для выполнения любого вида AOT с помощью Xamarin.Android требовался Android. NDK (загрузка нескольких ГБ). У нас не установлен андроид Приложение NDK, построенное на базе AOT,сделать это возможным。

мы за dotnet new android, maui, иmaui — встроенные файлы конфигурации для шаблонов блазор, которые приносят пользу большинству приложений. Если ты хочешь В .NET Чтобы записать собственный профиль в 6, вы можете попробовать наш экспериментальный Mono.Profiler. пакет Андроид. Мы работаем над полной поддержкой регистрации пользовательских профилей в будущей версии .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

▌Хранилище в одном файле сборки

Раньше, если вы просматривали содержимое Release android .apk в своей любимой утилите для работы с zip-файлами, вы могли видеть, что сборки .NET расположены по адресу:

Язык кода:javascript
копировать
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 сборки в приложении. Это реализовано на C/C++ в рабочей нагрузке Android.,Используйте обратные вызовы, предоставляемые средой выполнения Mono, для загрузки сборки. Приложение MAUI имеет много сборок,Итак, мы представили новую функцию $(androidUseAssemblyStore),Эта функция включена по умолчанию в версии Release.

После этого изменения вы получите:

Язык кода:javascript
копировать
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сборкаприложениеоказал значительное влияние。

Если вам нужно проверить IL этих сборок в скомпилированном приложении Android, мы создали инструмент чтения хранилища сборок для «распаковки» этих файлов.

Другой вариант — отключить эти настройки при сборке приложения:

Язык кода:javascript
копировать
dotnet build -c Release -p:AndroidUseAssemblyStore=false -p:Android EnableAssemblyCompression=false

Таким образом, вы можете распаковать полученный файл .apk с помощью вашего любимого инструмента сжатия и проверить сборку .NET с помощью такого инструмента, как ILSpy. Это отличный способ диагностировать проблемы триммера/линкера.

Дополнительные сведения об этом улучшении см. в разделе 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

Когда объект C# создается в Java, вызывается небольшая оболочка Java, например:

Язык кода:javascript
копировать
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#. Для каждого метода Java, который вы переопределяете в C#, вы получаете такой метод.

Когда фактический метод Java onCreate() вызывается как действие Android:

Язык кода:javascript
копировать
public void onCreate (Android.os.Bundle p0)
{
    n_onCreate (p0);
}

private native void n_onCreate (Android.os.Bundle p0);

С помощью различных магических действий и жестов n_onCreate вызывается в среду выполнения Mono и вызывает метод OnCreate() в C#.

Код для разделения списков методов, разделенных \nи:-delimited, был написан на заре Xamarin с использованием string.Split(). Можно сказать,Span<T>не существовало в то время,Но мы можем использовать его сейчас! Это повышает стоимость любого класса C#, который наследует класс Java!,Поэтому это лучший выбор, чем .NET. Более широкое улучшение MAUI.

Вы можете спросить: «Зачем вообще использовать строки?» Использование массивов Java, похоже, оказывает большее влияние на производительность, чем разделение строк. В наших тестах вызов JNI для получения элементов массива Java работал хуже, чем строки. Новое использование Split и Span. У нас есть некоторые идеи о том, как перестроить это в будущих версиях .NET.

Помимо .NET 6, это изменение также включено в последнюю версию Android для текущих клиентов Xamarin.

Дополнительные сведения об этом улучшении см. в разделе 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 у нас был довольно сложный способ вызова конструкторов C# из Java.

Во-первых, у нас есть несколько вызовов отражения, которые происходят при запуске:

Язык кода:javascript
копировать
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, и в

Язык кода:javascript
копировать
Передайте экземпляр cinfo в System.Reflection.ConstructorInfo:
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, а Object[] — любым аргументом этого конкретного конструктора C#. Emit требует значительных затрат при первом использовании при запуске и при каждом последующем вызове.

После тщательного анализа мы можем сделать поле дескриптора внутренним и упростить этот код:

Язык кода:javascript
копировать
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. Handle требуется для любого взаимодействия Java внутри конструктора (например, для вызова других методов Java в классе), а также для вызова любого базового конструктора Java.

Новый код значительно улучшает любой конструктор C#, вызываемый из Java, поэтому это конкретное изменение улучшает не только .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 и методы

Когда вы переопределяете метод Java в C#, например:

Язык кода:javascript
копировать
public class MainActivity : Activity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
         base.OnCreate(savedInstanceState);
         //...
    }
}

В процессе преобразования Java в C# мы должны инкапсулировать методы C# для обработки исключений, таких как:

Язык кода:javascript
копировать
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:

Язык кода:javascript
копировать
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 раз как дотнет new mauiприложение!

Мы поняли, что на самом деле можем написать строго типизированный «быстрый путь» для каждого универсального типа делегата вместо использования System.Reflection.Emit. Существует сгенерированный делегат, соответствующий каждой подписи:

Язык кода:javascript
копировать
void OnCreate(Bundle savedInstanceState);
// Maps to *JNIEnv, JavaClass, Bundle
// Internal to each assembly
internal delegate void _JniMarshal_PPL_V(IntPtr, IntPtr, IntPtr);

Таким образом, мы можем перечислить все используемые дотнеты. подпись приложения maui,например:

Язык кода:javascript
копировать
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.

  • System.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

▌Обновленные API Java.Interop.

Исходный API Xamarin в Java.Interop.dll — это этот API:

  • JNIEnv.CallStaticObjectMethod

«Новый метод», вызываемый в Java, занимает меньше памяти за вызов:

  • JniEnvironment.StaticMethods.CallStaticObjectMethod

При создании привязок C# для методов Java во время сборки по умолчанию используется более новый/более быстрый метод — он уже некоторое время доступен в Xamarin.Android. Раньше в проектах привязки Java можно было установить для $(AndroidCodegenTarget) значение XAJavaInterop1, что кэшировало и повторно использовало экземпляр jmethodID при каждом вызове. См. документацию java.interop для истории этой функции.

Другими проблемными местами являются «ручные» привязки. Эти методы, как правило, тоже часто используются, поэтому стоит их исправить!

Несколько примеров улучшения этой ситуации:

  • JNIEnv.FindClass() в xamarin-android#6805
  • JavaList и JavaList<T>существовать xamarin-android#6812
  • Обновленные API Java.Interop.: 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

При передаче массива C# туда и обратно в Java на промежуточном этапе необходимо скопировать массив, чтобы соответствующая среда выполнения могла получить к нему доступ. На самом деле это ситуация, связанная с опытом разработчиков, поскольку ожидается, что разработчики C# будут писать такие вещи:

Язык кода:javascript
копировать
var array = new int[] { 1, 2, 3, 4};
MyJavaMethod (array);

Что будет сделано в MyJavaMethod:

Язык кода:javascript
копировать
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 необходимо использовать для элементов массива.

Существует проблема с конкретным API-интерфейсом Android, используемым новым проектом dotnet maui:

Язык кода:javascript
копировать
public ColorStateList (int[][]? states, int[]? colors)

Найден многомерный массив int[][] с доступом к «карте типов» для каждого элемента. Мы можем видеть это во многих случаях, когда включено дополнительное ведение журнала:

Язык кода:javascript
копировать
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

▌Использование Glide для изображений Android

Glideсовременныйandroidприложение Рекомендуемая библиотека загрузки изображений。Google Docs даже рекомендует использовать его,Из-за встроенногоandroid Класс Bitmap может быть сложно использовать правильно. glidex.forms — это прототип использования Glide с Xamarin.Forms. но мы будем Glide Продвижение в будущее .NET MAUI «Способ» загрузки изображений в формате .

Чтобы уменьшить накладные расходы на взаимодействие JNI, реализация .NET MAUI Glide в основном написана на Java, например:

Язык кода:javascript
копировать
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# для обработки завершения в управляемом коде. В результате производительность изображений из Интернета должна быть значительно улучшена по сравнению с тем, что ранее было доступно в Xamarin.Forms.

Дополнительные сведения см. в dotnet/maui#759 и dotnet/maui#5198.

  • Использование Glide для изображений Android: 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:

Язык кода:javascript
копировать
public void setFoo(int foo);
public void setBar(int bar);

Эти методы взаимодействуют следующим образом:

Язык кода:javascript
копировать
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)

Язык кода:javascript
копировать
{
    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 В View мы можем создать метод с примерно 18 параметрами для создания представления. Последующие изменения свойств могут напрямую вызывать стандартный 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

▌Перенос XML Android на Java

Оглядываясь назад на результаты трассировки dotnet на Android, мы видим, что разумное количество времени тратится на:

Язык кода:javascript
копировать
20.32.ms mono.andorid!Andorid.Views.LayoutInflater.Inflate

Просмотрите трассировку стека,На самом деле время тратится на макет расширения Android/Java.,И В На стороне .NET никакой работы не происходит.

Если вы посмотрите на скомпилированные файлы .apk android и res/layouts/bottomtablayout. В Android Studio XML — это просто XML. Лишь немногие идентификаторы преобразуются в целые числа. Это означает, что Android должен анализировать XML и создавать объекты Java с помощью API-интерфейса отражения Java. Кажется, мы можем добиться более высокой производительности без использования XML?

Используя стандартное сравнение BenchmarkDotNet, мы обнаружили, что использование Android Layout работает даже хуже, чем использование 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 представлен более простой макет с навигацией по нижней вкладке:

Язык кода:javascript
копировать
<?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, например:

Язык кода:javascript
копировать
@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)

Это позволяет нам переключаться с C# на Java только 4 раза при создании навигации по нижней вкладке на Android. Это также позволяет операционной системе Android «раздувать» объекты Java, пропуская загрузку и анализ .xml. Мы реализовали эту идею в dotnet/maui, удалив все вызовы LayoutInflater.Inflate() при запуске.

Подробные сведения об этих улучшениях см. в dotnet/maui#5424, dotnet/maui#5493 и dotnet/maui#5528.

  • будет андроид 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

хостинг предоставляет универсальный хост .NET,Используется для управления внедрением зависимостей, ведением журнала, настройкой и жизненным циклом в приложении .NET. Это влияет на время запуска,Кажется неуместным переносить приложение.

Имеет смысл удалить использование Microsoft.Extensions.Hosting из .NET MAUI. .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

▌Уменьшить инициализацию оболочки при запуске.

Xamarin. Forms Shell — это режим кроссплатформенной навигации по приложениям. Этот режим В .NET Представленный в MAUI, он рекомендуется в качестве способа создания приложения по умолчанию.

Когда мы узнали о стоимости использования оболочки при запуске (для Xamarin, Xamarin.form и .NET MAUI), мы обнаружили несколько областей, которые можно оптимизировать:

  • Не разрешайте маршруты при запуске — подождите, пока не произойдет навигация, требующая их.
  • Если вы не предоставляете строку запроса для навигации, просто пропустите код, обрабатывающий строку запроса. Это приведет к удалению путей кода, которые злоупотребляют System.Reflection.
  • Если на странице нет видимого BottomNavigationView, не устанавливайте пункты меню или какие-либо элементы внешнего вида.

Подробные сведения об этом улучшении см. в dotnet/maui#5262.

  • Уменьшить инициализацию оболочки при запуске: 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:

Язык кода:javascript
копировать
32.19ms Microsoft.Maui!Microsoft.Maui.FontManager.CreateTypeface(System.ValueTuple`3<string, Microsoft.Maui.FontWeight, bool>)

При проверке кода он выполняет больше работы, чем необходимо:

1. Сохраните файл androidAsset во временную папку.

2. Используйте API Android, Typeface.CreateFromFile() для загрузки файлов.

На самом деле мы можем использовать Android API Typeface.CreateFromAsset() напрямую, вообще не используя временные файлы.

Дополнительные сведения об этом улучшении см. в 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}:

Язык кода:javascript
копировать
<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.

Это приводит к более качественному/быстрому созданию IL-кодов из файлов .xaml.

  • Использование преобразователей компилятора в 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

назначенный

Разбор (ранее)

99.13 ns

0.281 ns

0.235 ns

0.0267

168 B

Разобрать (после)

52.54 ns

0.292 ns

0.259 ns

0.0051

32 B

能够существоватьReadonlySpan<char> Использование операторов переключения в dotnet/csharpang#1881 еще больше улучшит эту ситуацию в будущих версиях .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

▌Не используйте сравнения строк с учетом языка и региональных параметров.

Оглядываясь назад на результаты трассировки DotNet из нового проекта NAUI, можно увидеть истинную цену первого сравнения строк с учетом культуры на Android:

Язык кода:javascript
копировать
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.

Например, если у вас есть:

Язык кода:javascript
копировать
if (text.StartsWith("f"))
{
    // do something
}

В этом случае вы можете просто сделать это:

Язык кода:javascript
копировать
if (text.StartsWith("f", StringComparision.Ordinal))
{
    // do something
}

Если выполняется на протяжении всего приложения,System.Globalization.CultureInfo.CurrentCulture можно избежать вызова,И может немного улучшить общую скорость операторов If.

Чтобы решить эту ситуацию в репозитории dotnet/maui, мы ввели правила анализа кода для их фиксации:

Язык кода:javascript
копировать
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

▌Ленивое создание журналов

API-интерфейс ConfigurationFonts() тратит некоторое время при запуске на выполнение некоторой работы, которую можно отложить на потом. Мы также можем улучшить общее использование инфраструктуры журналирования в Microsoft.Extensions.

Некоторые из улучшений, которые мы сделали, заключаются в следующем:

  • Отложите создание классов «регистраторов» до тех пор, пока они вам не понадобятся.
  • Встроенная инфраструктура ведения журнала отключена по умолчанию и должна быть включена явно.
  • Отложите вызов Path.GetTempPath() в Android EmbeddedFontLoader до тех пор, пока он не понадобится.
  • Не используйте 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, служба регистрации, например:

Язык кода:javascript
копировать
IServiceCollection services /* ... */;
services.TryAddSingleton<IFooService, FooService>();

Microsoft.Extensions необходимо выполнить некоторые действия System.Reflection, чтобы создать первый экземпляр FooService. Стоит отметить вывод трассировки dotnet на Android.

Вместо этого, если вы сделаете это:

Язык кода:javascript
копировать
// 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/anonymous.,без необходимости использования системы. отражение.

Мы улучшили все dotnet/maui и использовали запрещенный анализатор, чтобы никто случайно не использовал более медленную перегрузку TryAddSingleton().

Дополнительные сведения об этом улучшении см. в dotnet/maui#5290.

  • Внедрение зависимостей с использованием фабричного метода: https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#use-factory-methods-for-dependency-injection?ocid=AID3045631
  • bannedapianalyzer: 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 не используется многими мобильными приложениями,А создать его очень дорого (например!,Около 7,59мс на андроиде)

В .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–7 мс:

Язык кода:javascript
копировать
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallsiteFactory.ValidateTrimmingAnnotations()

Атрибут MSBuild $(verifydependentinjectionopengenericservicetrimability) запускает этот метод. Этот переключатель функции гарантирует правильное использование динамически доступных элементов для включения универсальных типов при внедрении зависимостей.

В основах .NET В SDK при публикации =true, переключатель будет включен. Однако приложение Android не устанавливает публикацию в версии отладки. =true, поэтому разработчик пропустил эту проверку.

Напротив,В опубликованном приложении,Мы не хотим оплачивать стоимость этой проверки. Поэтому этот переключатель функции должен быть отключен в версии Release.

Дополнительные сведения об этом улучшении см. в разделах xamarin-android#6727 и xamarin-macios#14130.

  • по умолчаниюVerifyDependencyInjectionOpenGenericServiceTrimmability: https://devblogs.microsoft.com/dotnet/ Performance-improvements-in-dotnet-maui/#default-verifydependentinjectionopengenericservicetrimmability?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 для каждого метода (см. нашу документацию), например:

Язык кода:javascript
копировать
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. Лучшие варианты выбора времени для подкастов. Кажется, это именно то, чего хотят разработчики в . net MAUIприложение Обычно используется вapi。

Чтобы гарантировать наличие этих методов в файле конфигурации AOT, мы используем эти API в dotnet/maui.

Язык кода:javascript
копировать
_=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-отчет:

Язык кода:javascript
копировать
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&)

Это привело к дальнейшим дополнениям:

Язык кода:javascript
копировать
var split = "foo;bar".Split(';');
var x = int.Parse("999");
x.ToString();

Мы внесли аналогичные изменения в Color.Parse() и Connectivity.NETworkAccess. Информация об устройстве. Идиома, AppInfo. .СЕТЬ MAUIприложениеследует часто использовать вrequestdtheme。

Подробные сведения об этих улучшениях см. в dotnet/maui#5559, dotnet/maui#5682 и dotnet/maui#6834.

если ты хочешь В .NET Чтобы записать собственный профиль AOT в 6, вы можете попробовать наш экспериментальный пакет 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 при запуске.,чтобы убедиться, что MVID размещенной сборки .NET (например, Foo.dll) соответствует образу AOT (libFoo.dll.so). В большинстве приложений .NET,Некоторые изображения AOT, возможно, не потребуется загружать позже.

В Mono появился новый параметр - aot-lazy-assembly-load или mono_opt_aot_lazy_assembly_load, рабочие нагрузки Android могут выбирать. Мы обнаружили, что это улучшило время запуска нового проекта maui на Pixel 6 Pro примерно на 25 мс.

По умолчанию это включено, но при желании вы можете установить его у себя. Отключите этот параметр в csproj через:

Язык кода:javascript
копировать
<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.

Результаты трассировки dotnet из приложения MAUI, показывающие, что загрузка системы при первой кодировке UTF32иLatin1 заняла около 7 мс. Использование Uri API:

Язык кода:javascript
копировать
namespace System
{
    internal static class UriHelper
    {
        internal static readonly Encoding s_noFallbackCharUTF8 = Encoding.GetEncoding(
            Encoding.UTF8.CodePage, new EncoderReplacementFallback(""), new DecoderReplacementFallback(""));

Это поле было случайно оставлено на месте. Любое использование System.Uri можно улучшить, просто удалив поле s_noFallbackCharUTF8. или связанный 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
boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose