Výkon pro různé platformy

Nízký výkon aplikace prezentuje mnoho způsobů. Může dojít k tomu, že aplikace přestane reagovat, může způsobit pomalé posouvání a může snížit výdrž baterie. Optimalizace výkonu však zahrnuje více než pouze implementaci efektivního kódu. Také je nutné vzít v úvahu činnost uživatele při výkonu aplikace. Například zajistěte, aby se operace prováděly bez blokování uživatele, aby mohli provádět další aktivity, což může pomoci zlepšit uživatelské prostředí.

Použití profileru

Při vývoji aplikace je důležité se pouze pokusit o optimalizaci kódu po jeho profilování. Profilace je způsob, jak určit, kde budou optimalizace kódu mít největší dopad na omezení problémů s výkonem. Profiler sleduje využití paměti aplikace a zaznamenává dobu běhu metod v aplikaci. Tato data pomáhají procházet cesty provádění aplikace a náklady na spuštění tohoto kódu, aby bylo možné zjistit nejlepší příležitosti pro optimalizaci.

Xamarin Profiler bude měřit, vyhodnocovat a pomáhat s vyhledáváním problémů souvisejících s výkonem v aplikaci. dá se použít k profilování aplikací xamarin. iOS a xamarin. Android v rámci Visual Studio pro Mac nebo Visual Studio. Další informace o Xamarin Profiler najdete v tématu Úvod do Xamarin Profiler.

Při profilování aplikace doporučujeme následující osvědčené postupy:

  • Vyhněte se profilování aplikace v simulátoru, protože simulátor může derušit výkon aplikace.
  • V ideálním případě by profilace měla být prováděna na různých zařízeních, protože měření výkonu na jednom zařízení nezobrazuje vždy výkonové charakteristiky jiných zařízení. Na zařízení, které má nejnižší předpokládanou specifikaci, ale musí být profilace přinejmenším provedena.
  • Ukončete všechny ostatní aplikace, abyste zajistili, že se měří úplný dopad aplikace na profilování, nikoli na ostatní aplikace.

Uvolnění prostředků IDisposable

IDisposableRozhraní poskytuje mechanismus pro uvolnění prostředků. Poskytuje Dispose metodu, která by měla být implementována pro explicitní uvolnění prostředků. IDisposable není destruktor a měl by být implementován pouze v následujících případech:

  • Pokud třída vlastní nespravované prostředky. Typické nespravované prostředky, které vyžadují uvolnění vložených souborů, datových proudů a síťových připojení.
  • Když třída vlastní spravované IDisposable prostředky.

Typ příjemci pak může zavolat IDisposable.Dispose implementaci k uvolnění prostředků, pokud už instance není potřeba. Existují dva přístupy k dosažení tohoto:

  • Vybalením IDisposable objektu v using příkazu.
  • Zabalením volání do IDisposable.Disposetry/finally bloku.

Zabalení objektu IDisposable v příkazu Using

Následující příklad kódu ukazuje, jak zabalit IDisposable objekt v using příkazu:

public void ReadText (string filename)
{
  ...
  string text;
  using (StreamReader reader = new StreamReader (filename)) {
    text = reader.ReadToEnd ();
  }
  ...
}

StreamReaderTřída implementuje IDisposable a using příkaz nabízí pohodlný syntax, která StreamReader.DisposeStreamReader předá do oboru mimo rozsah metodu, která volá metodu objektu. V rámci using bloku StreamReader je objekt jen pro čtení a nelze ho znovu přiřadit. usingPříkaz také zajišťuje, že Dispose Metoda je volána i v případě, že dojde k výjimce, protože kompilátor implementuje pro blok převodní jazyk (IL) try/finally .

Zabalení volání IDisposable. Dispose v bloku try/finally

Následující příklad kódu ukazuje, jak zabalit volání do IDisposable.Disposetry/finally bloku:

public void ReadText (string filename)
{
  ...
  string text;
  StreamReader reader = null;
  try {
    reader = new StreamReader (filename);
    text = reader.ReadToEnd ();
  } finally {
    if (reader != null) {
      reader.Dispose ();
    }
  }
  ...
}

StreamReaderTřída implementuje IDisposable a finally blok volá StreamReader.Dispose metodu pro uvolnění prostředku.

Další informace naleznete v tématu rozhraní IDisposable.

Odhlásit odběr událostí

Aby se zabránilo nevracení paměti, události by se měly odhlásit z odběru před vyřazením objektu předplatitele. Dokud nedojde k odběru události, delegát pro událost v objektu publikování obsahuje odkaz na delegáta, který zapouzdřuje obslužnou rutinu události odběratele. Pokud objekt publikování obsahuje tento odkaz, uvolňování paměti neuvolní paměť objektů odběratele.

Následující příklad kódu ukazuje, jak zrušit odběr události:

public class Publisher
{
  public event EventHandler MyEvent;

  public void OnMyEventFires ()
  {
    if (MyEvent != null) {
      MyEvent (this, EventArgs.Empty);
    }
  }
}

public class Subscriber : IDisposable
{
  readonly Publisher publisher;

  public Subscriber (Publisher publish)
  {
    publisher = publish;
    publisher.MyEvent += OnMyEventFires;
  }

  void OnMyEventFires (object sender, EventArgs e)
  {
    Debug.WriteLine ("The publisher notified the subscriber of an event");
  }

  public void Dispose ()
  {
    publisher.MyEvent -= OnMyEventFires;
  }
}

SubscriberTřída se odhlásí od události ve své Dispose metodě.

K referenčním cyklům může docházet také při použití obslužných rutin událostí a syntaxe lambda, protože výrazy lambda mohou odkazovat a udržet objekty v aktivním stavu. Proto odkaz na anonymní metodu může být uložen v poli a použít k odhlášení odběru události, jak je znázorněno v následujícím příkladu kódu:

public class Subscriber : IDisposable
{
  readonly Publisher publisher;
  EventHandler handler;

  public Subscriber (Publisher publish)
  {
    publisher = publish;
    handler = (sender, e) => {
      Debug.WriteLine ("The publisher notified the subscriber of an event");
    };
    publisher.MyEvent += handler;
  }

  public void Dispose ()
  {
    publisher.MyEvent -= handler;
  }
}

handlerPole udržuje odkaz na anonymní metodu a používá se pro odběr událostí a zrušení odběru.

Použití slabých odkazů k zamezení neúmrtnosti objektů

Poznámka

Vývojáři iOS by si měli projít dokumentaci týkající se zamezení cyklických odkazů v iOS , aby jejich aplikace účinně používala paměť.

Zpoždění nákladů na vytváření objektů

Opožděná inicializace se dá použít k odložení vytvoření objektu, dokud se nepoužije první. Tato technika se primárně používá ke zvýšení výkonu, vyhněte se výpočtu a omezení požadavků na paměť.

Zvažte použití opožděné inicializace pro objekty, které jsou nákladné pro vytvoření v těchto dvou scénářích:

  • Aplikace pravděpodobně objekt nepoužívá.
  • Další náročné operace musí být dokončeny před vytvořením objektu.

Lazy<T>Třída se používá k definování opožděně inicializovaného typu, jak je znázorněno v následujícím příkladu kódu:

void ProcessData(bool dataRequired = false)
{
  Lazy<double> data = new Lazy<double>(() =>
  {
    return ParallelEnumerable.Range(0, 1000)
                 .Select(d => Compute(d))
                 .Aggregate((x, y) => x + y);
  });

  if (dataRequired)
  {
    if (data.Value > 90)
    {
      ...
    }
  }
}

double Compute(double x)
{
  ...
}

Při prvním otevření vlastnosti dojde k opožděné inicializaci Lazy<T>.Value . Zabalený typ je vytvořen a vrácen při prvním přístupu a uložený pro jakýkoliv budoucí přístup.

Další informace o opožděné inicializaci naleznete v tématu opožděná inicializace.

Implementace asynchronních operací

Rozhraní .NET poskytuje asynchronní verze mnoha jeho rozhraní API. Na rozdíl od synchronních rozhraní API asynchronní rozhraní API zajistí, aby aktivní vlákno spuštění nikdy neblokovalo volající vlákno na významné množství času. Proto při volání rozhraní API z vlákna uživatelského rozhraní, použijte asynchronní rozhraní API, pokud je k dispozici. Tím se ponechá vlákno uživatelského rozhraní odblokované, což vám pomůže zlepšit činnost uživatele s aplikací.

Kromě toho by měly být dlouho běžící operace provedeny ve vlákně na pozadí, aby nedošlo k zablokování vlákna uživatelského rozhraní. Rozhraní .NET poskytuje asyncawait klíčová slova a, která umožňují psaní asynchronního kódu, který spouští dlouhotrvající operace ve vlákně na pozadí a přistupuje k výsledkům při dokončení. Nicméně zatímco dlouho běžící operace lze provádět asynchronně s await klíčovým slovem, nezaručuje to, že operace bude spuštěna ve vlákně na pozadí. Místo toho je možné to provést předáním dlouhotrvající operace do Task.Run , jak je znázorněno v následujícím příkladu kódu:

public class FaceDetection
{
  ...
  async void RecognizeFaceButtonClick(object sender, EventArgs e)
  {
    await Task.Run(() => RecognizeFace ());
    ...
  }

  async Task RecognizeFace()
  {
    ...
  }
}

RecognizeFaceMetoda se spouští ve vlákně na pozadí s metodou, která RecognizeFaceButtonClick čeká na RecognizeFace dokončení metody před pokračováním.

Dlouhodobě běžící operace by také měly podporovat zrušení. Například pokračování dlouhé běžící operace může být zbytečné, pokud uživatel přejde v rámci aplikace. Vzor pro implementaci zrušení je následující:

  • Vytvořte CancellationTokenSource instanci. Tato instance bude spravovat a odesílat oznámení o zrušení.
  • CancellationTokenSource.TokenHodnotu vlastnosti předejte každému úkolu, který by měl být zrušen.
  • Poskytněte mechanismus pro každý úkol, který bude reagovat na zrušení.
  • Zavolejte CancellationTokenSource.Cancel metodu pro poskytnutí oznámení o zrušení.

Důležité

CancellationTokenSourceTřída implementuje IDisposable rozhraní a CancellationTokenSource.Dispose Metoda by měla být vyvolána, jakmile CancellationTokenSource je instance dokončena.

Další informace najdete v tématu Přehled asynchronní podpory.

Použití systému uvolňování paměti SGen

Spravované jazyky, jako je C#, používají uvolňování paměti k uvolnění paměti, která je přidělena objektům, které se již nepoužívají. Existují dva sběrače paměti používané platformou Xamarin:

  • Sgen – Toto je systém uvolňování paměti pro generování paměti a je výchozím systémem uvolňování paměti na platformě Xamarin.
  • Boehm – jedná se o konzervativní systém uvolňování paměti, který není generace. Je to výchozí systém uvolňování paměti používaný pro aplikace Xamarin. iOS, které používají Classic API.

SGen využívá jednu ze tří hald k přidělení prostoru pro objekty:

  • V této části je místo, kde jsou přiděleny nové malé objekty. V případě nedostatku místa v mateřské sadě dojde k vedlejšímu uvolňování paměti. Všechny živé objekty budou přesunuty do hlavní haldy.
  • Hlavní halda – Toto je místo, kde se uchovávají dlouhodobé objekty. Pokud není v hlavní haldě dostatek paměti, dojde k velkému uvolňování paměti. Pokud velkému uvolňování paměti selže uvolnění dostatečné paměti, SGen požádá o další paměť.
  • Místo large object – Toto je místo, kde se uchovávají objekty, které vyžadují více než 8000 bajtů. Velké objekty nebudou v školách začínat, ale místo toho se přidělí v této haldě.

Jednou z výhod SGen je, že čas, který je potřeba k provedení menšího uvolňování paměti, je úměrný počtu nových živých objektů, které byly vytvořeny od posledního menšího uvolňování paměti. Tím se sníží dopad uvolňování paměti na výkon aplikace, protože tyto menší uvolňování paměti bude trvat méně času než hlavní uvolňování paměti. Hlavní uvolňování paměti bude stále docházet, ale méně často.

Systém uvolňování paměti SGen je výchozí v Xamarin.iOS 9.2.1 a vyšším, a proto se použije automaticky. Upozorňujeme, že možnost změnit systém uvolňování paměti byla z novějších verzí systému Visual Studio. Další informace najdete v tématu Nový systém počítání odkazů.

Snížení tlaku na systém uvolňování paměti

Když SGen spustí uvolňování paměti, zastaví vlákna aplikace, zatímco znovu získá paměť. Zatímco se paměť umistí, může v uživatelském rozhraní dojít ke krátkému pozastavení nebo zamkčení aplikace. Jak je tato pauza znatelná, závisí na dvou faktorech:

  1. Frequency – četnost uvolňování paměti. Frekvence uvolňování paměti se zvýší s tím, jak je mezi kolekcemi přiděleno více paměti.
  2. Duration – doba trvání jednotlivých uvolňování paměti. To je přibližně úměrné počtu shromažďovaných živých objektů.

Souhrnně to znamená, že pokud je přiděleno mnoho objektů, ale nezůdrží se, bude k dispozici mnoho krátkých uvolňování paměti. A naopak, pokud jsou nové objekty přiděleny pomalu a objekty zůstanou v paměti, bude méně, ale delší uvolňování paměti.

Pokud chcete snížit tlak na systém uvolňování paměti, postupujte podle těchto pokynů:

  • Vyhněte se uvolňování paměti v úzce smyčkách pomocí fondů objektů. To je zvlášť důležité pro hry, které potřebují předem vytvořit většinu svých objektů.
  • Explicitně uvolněte prostředky, jako jsou datové proudy, síťová připojení, velké bloky paměti a soubory, jakmile už je nepotřebujete. Další informace najdete v tématu Release IDisposable Resources.
  • Zrušte registraci obslužných rutin událostí, jakmile už nejsou potřeba, aby bylo možné shromažďovat objekty. Další informace najdete v tématu Odhlášení odběru událostí.

Zmenšete velikost aplikace.

Je důležité pochopit proces kompilace na jednotlivých platformách, abyste pochopili, odkud pochází velikost spustitelného souboru aplikace:

  • Aplikace pro iOS jsou předem kompilované do jazyka sestavení ARM. Součástí je rozhraní .NET Framework, kdy se nepoužívané třídy odčtou jenom v případě, že je povolená odpovídající možnost linkeru.
  • Aplikace pro Android se kompilují do přechodného jazyka (IL) a zabalí se s kompilací MonoVM a JIT (just-in-time). Nepoužívané třídy rozhraní jsou odříznuty pouze v případě, že je povolena odpovídající možnost linkeru.
  • Windows Phone aplikace jsou zkompilovány do IL a spuštěny integrovaným runtime.

Kromě toho, pokud aplikace ve velké velikosti využívá obecné typy, konečná velikost spustitelného souboru se dále zvýší, protože bude obsahovat nativně kompilované verze obecných možností.

Za účelem snížení velikosti aplikací platforma Xamarin zahrnuje linker jako součást nástrojů sestavení. Linker je ve výchozím nastavení zakázaný a musí být povolený v možnostech projektu pro aplikaci. V době sestavení provede statickou analýzu aplikace, aby určila, které typy a členy aplikace skutečně používá. Pak z aplikace odebere všechny nepoužívané typy a metody.

Následující snímek obrazovky ukazuje možnosti linkeru v Visual Studio pro Mac pro projekt Xamarin.iOS:

Možnosti linkeru pro Xamarin.iOS

Následující snímek obrazovky ukazuje možnosti linkeru v Visual Studio pro Mac pro projekt Xamarin.Android:

Možnosti linkeru pro Xamarin.Android

Linker poskytuje tři různá nastavení, která řídí jeho chování:

  • Bez propojení – linker nebude odebrat žádné nepoužívané typy a metody. Z důvodů výkonu se jedná o výchozí nastavení pro sestavení pro ladění.
  • Pouze sady SDK rozhraní nebo sestavení sady SDK – Toto nastavení zmenšuje pouze velikost sestavení dodaného Xamarinem. Uživatelský kód nebude ovlivněn.
  • Propojení všech sestavení – jedná se o agresivnější optimalizaci, která bude cílit na sestavení sady SDK a uživatelský kód. U vazeb se odeberou nepoužívané zálohovací pole a každá instance (nebo vázané objekty) budou jednodušší a spotřebovávají méně paměti.

Funkce Propojit všechna sestavení by se měla používat obezřetně, protože může aplikaci neočekávaně přerušit. Statická analýza, která je provedena linkerem, nemusí správně identifikovat veškerý požadovaný kód, což vede k příliš mnoho kódu odebrané z kompilované aplikace. Tato situace se projeví pouze za běhu, když dojde k chybě aplikace. Z tohoto důvodu je důležité důkladně otestovat aplikaci po změně chování linkeru.

Pokud testování odhalí, že linker nesprávně odebral třídu nebo metodu, je možné označit typy nebo metody, které nejsou staticky odkazované, ale aplikace je vyžaduje pomocí jednoho z následujících atributů:

  • Xamarin.iOS.Foundation.PreserveAttribute – Tento atribut je pro projekty Xamarin.iOS.
  • Android.Runtime.PreserveAttribute – Tento atribut je pro projekty Xamarin.Android.

Například může být nutné zachovat výchozí konstruktory typů, které jsou dynamicky vytvořeny instance. Také použití serializace XML může vyžadovat, že jsou zachovány vlastnosti typů.

Další informace najdete v tématu Linker pro iOS aLinker pro Android.

Další techniky redukce velikosti

Existuje široká škála architektur procesoru, které pojí mobilní zařízení. Xamarin.iOS a Xamarin.Android proto vytvářejí binární soubory s tučnými číty, které obsahují zkompilovanou verzi aplikace pro každou architekturu procesoru. Tím se zajistí, že mobilní aplikace může běžet na zařízení bez ohledu na architekturu procesoru.

Následující kroky můžete použít k dalšímu snížení velikosti spustitelného souboru aplikace:

  • Ujistěte se, že je vytvořeno sestavení pro vydání.
  • Snižte počet architektur, pro které je aplikace vytvořená, abyste se vyhnuli vzniku binárního souboru FAT.
  • Ujistěte se, že se používá kompilátor LLVM k vygenerování optimalizovanějšího spustitelného souboru.
  • Zmenšete velikost spravovaného kódu aplikace. Toho lze dosáhnout povolením linkeru u každého sestavení (Propojit vše pro projekty iOS a Propojit všechna sestavení pro projekty Android).

Aplikace pro Android je také možné rozdělit do samostatného souboru APK pro každý ABI ("architektura"). Další informace najdete v tomto blogovém příspěvku: How To Keep Your Android App Size Down.

Optimalizace prostředků obrázků

Image jsou některé z nejdražších prostředků, které aplikace používají, a často se zachycují ve vysokém rozlišení. I když se tím vytvoří velmi podrobné obrázky, aplikace, které tyto obrázky zobrazují, obvykle vyžadují větší využití procesoru k dekódování obrázku a více paměti pro uložení dekódované image. Dekódování obrázku ve vysokém rozlišení v paměti je plýtvání, když se jeho velikost pro zobrazení zmenší na menší velikost. Místo toho snižte využití procesoru a využití paměti tím, že vytvoříte více verzí rozlišení uložených imagí, které se blíží předpokládaným velikostem zobrazení. Například obrázek zobrazený v zobrazení seznamu by měl mít s největší pravděpodobností nižší rozlišení než obrázek zobrazený na celé obrazovce. Kromě toho je možné načíst verze imagí ve vysokém rozlišení s horizontálním snížením velikosti, aby je bylo možné efektivně zobrazit s minimálním dopadem na paměť. Další informace najdete v tématu Efektivní načítání velkých rastrových obrázků.

Bez ohledu na rozlišení obrázku může zobrazení prostředků obrázků výrazně zvýšit nároky aplikace na paměť. Proto by se měly vytvářet jenom v případě potřeby a měly by se uvolnit, jakmile je aplikace už nepotřebuje.

Zkrácení doby aktivace aplikace

Všechny aplikace mají období aktivace, což je čas mezi zahájením aplikace a tím, kdy je aplikace připravená k použití. Tato doba aktivace poskytuje uživatelům jejich první dojem na aplikaci, a proto je důležité zkrátit dobu aktivace a vnímání aplikace uživatelem, aby získali pozitivní první dojem z aplikace.

Než aplikace zobrazí počáteční uživatelské rozhraní, měla by uživateli poskytnout úvodní obrazovku, která bude indikovat, že se aplikace spouští. Pokud aplikace nemůže rychle zobrazit počáteční uživatelské rozhraní, měla by se pomocí úvodní obrazovky informovat uživatele o průběhu aktivace, aby se ujistil, že aplikace není zaměněná. Tato jistotu může být indikátor průběhu nebo podobná kontrola.

Během období aktivace aplikace spouští logiku aktivace, která často zahrnuje načítání a zpracování prostředků. Dobu aktivace je možné zkrátit tím, že zajistíte, že se požadované prostředky zabalí do aplikace místo toho, aby se načítaly vzdáleně. V některých případech může být například vhodné během doby aktivace načíst místně uložená zástupná data. Jakmile se zobrazí počáteční uživatelské rozhraní a uživatel bude moct s aplikací pracovat, zástupná data je možné postupně nahradit ze vzdáleného zdroje. Kromě toho by logika aktivace aplikace měla provádět pouze práci, která je nutná k tomu, aby uživatel začal aplikaci používat. To může pomoct, pokud zpoždí načítání dalších sestavení, protože sestavení se načítají při jejich prvním použití.

Omezení komunikace webové služby

Připojení k webové službě z aplikace může mít vliv na výkon aplikace. Například vyšší využití šířky pásma sítě bude mít za následek zvýšené využití baterie zařízení. Kromě toho mohou uživatelé používat aplikaci v prostředí s omezenou šířkou pásma. Proto je rozumné omezit využití šířky pásma mezi aplikací a webovou službou.

Jedním z přístupů ke snížení využití šířky pásma aplikace je komprese dat před jejich přenosem přes síť. Další využití procesoru v procesu komprese ale může také vést k vyššímu využití baterie. Proto byste tento kompromis měli pečlivě vyhodnotit, než se rozhodnete, jestli se mají komprimovaná data přesunout přes síť.

Dalším problémem, který je třeba vzít v úvahu, je formát dat, která se přesunou mezi aplikací a webovou službou. Dva primární formáty jsou jazyk XML (eXtensible Markup Language) (XML) a JavaScript Object Notation (JSON). XML je textový formát výměny dat, který vytváří relativně velké datové části, protože obsahuje velký počet formátovacích znaků. JSON je textový formát výměny dat, který vytváří kompaktní datové části, což má za výsledek snížení požadavků na šířku pásma při odesílání a přijímání dat. Proto je FORMÁT JSON často preferovaný pro mobilní aplikace.

Při přenosu dat mezi aplikací a webovou službou se doporučuje používat objekty DTO (Data Transfer Objects). Objekt DTO obsahuje sadu dat pro přenos přes síť. Díky použití DTO je možné v jednom vzdáleném volání přenášet více dat, což může pomoct snížit počet vzdálených volání provedených aplikací. Obecně platí, že vzdálené volání s větší datovou částí trvá podobně dlouho jako volání, které přenáší pouze malou datovou část.

Data načtená z webové služby by se měla ukládat do místní mezipaměti a data uložená v mezipaměti by se neměla opakovaně načítá z webové služby. Při použití tohoto přístupu by však měla být implementovaná vhodná strategie ukládání do mezipaměti, která aktualizuje data v místní mezipaměti, pokud se změní ve webové službě.

Souhrn

Tento článek popisuje a probíral techniky pro zvýšení výkonu aplikací sestavených pomocí platformy Xamarin. Souhrnně tyto techniky mohou výrazně snížit množství práce prováděné procesorem a množství paměti spotřebované aplikací.