Výkon napříč platformami

Nízký výkon aplikace se prezentuje mnoha způsoby. Může se zdát, že aplikace nereaguje, může způsobit pomalé posouvání a může snížit životnost baterie. Optimalizace výkonu ale zahrnuje více než jen implementaci efektivního kódu. Uživatelské zkušenosti s výkonem aplikace musí být také považovány za. Například zajištění, aby se operace spouštěly bez blokování uživatele v provádění jiných aktivit, můžou pomoct zlepšit prostředí uživatele.

Použití profileru

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

Xamarin Profiler bude měřit, vyhodnocovat a pomáhat najít problémy související s výkonem v aplikaci. Dá se použít k profilování aplikací Xamarin.iOS a Xamarin.Android z Visual Studio pro Mac nebo Visual Studio. Další informace o Xamarin Profiler naleznete v tématu Úvod do Xamarin Profiler.

Při profilaci aplikace se doporučuje následující osvědčené postupy:

  • Vyhněte se profilaci aplikace v simulátoru, protože simulátor může narušit výkon aplikace.
  • V ideálním případě by se profilace měla provádět na různých zařízeních, protože měření výkonu na jednom zařízení nebude vždy zobrazovat charakteristiky výkonu jiných zařízení. Profilace by se ale měla provádět na zařízení, které má nejnižší očekávanou specifikaci.
  • Zavřete všechny ostatní aplikace, abyste měli jistotu, že se měří úplný dopad profilované aplikace, a ne ostatní aplikace.

Uvolnění prostředků IDisposable

Toto IDisposable rozhraní poskytuje mechanismus pro uvolnění prostředků. Poskytuje metodu Dispose , která by se měla implementovat do explicitně vydaných prostředků. IDisposable není destruktor a měl by být implementován pouze za následujících okolností:

  • Když třída vlastní nespravované prostředky. Typické nespravované prostředky, které vyžadují uvolnění, zahrnují soubory, streamy a síťová připojení.
  • Když třída vlastní spravované IDisposable prostředky.

Příjemci typů pak můžou volat implementaci IDisposable.Dispose k volným prostředkům, pokud už instance není nutná. K dosažení tohoto cíle existují dva přístupy:

  • Zabalením objektu IDisposableusing do příkazu
  • Zabalením volání do IDisposable.Dispose/tryfinally bloku

Zabalení objektu IDisposable do příkazu using

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

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

Třída StreamReader implementuje IDisposablea using příkaz poskytuje pohodlnou syntaxi, která volá metodu StreamReader.DisposeStreamReader na objektu před tím, než přejde mimo rozsah. using V bloku StreamReader je objekt jen pro čtení a nelze ho znovu přiřadit. Příkaz using také zajišťuje, aby metoda byla volána i v případě, že Dispose dojde k výjimce, protože kompilátor implementuje zprostředkující jazyk (IL) pro try/finally blok.

Zabalení volání IDisposable.Dispose do bloku Try/Finally

Následující příklad kódu ukazuje, jak zabalit volání IDisposable.Dispose do try/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 ();
    }
  }
  ...
}

Třída StreamReader implementuje IDisposablea finally blok volá metodu StreamReader.Dispose k uvolnění prostředku.

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

Odhlášení odběru událostí

Aby nedošlo k nevracení paměti, měly by se události odhlásit před odstraněním objektu odběratele. Dokud se událost neodhlásí, delegát události v objektu publikování má odkaz na delegáta, který zapouzdřuje obslužnou rutinu události odběratele. Pokud objekt publikování uchovává tento odkaz, uvolňování paměti paměti odběratele nebude uvolnit.

Následující příklad kódu ukazuje, jak odhlásit 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;
  }
}

Třída Subscriber odhlásí odběr události ve své Dispose metodě.

K referenčním cyklům může dojít také při použití obslužných rutin událostí a syntaxe lambda, protože výrazy lambda můžou odkazovat na objekty a udržovat je naživu. Proto lze odkaz na anonymní metodu uložit do pole 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;
  }
}

Pole handler udržuje odkaz na anonymní metodu a používá se k odběru událostí a odhlášení odběru.

Použití slabýchodkazůch

Poznámka

Vývojáři pro iOS by si měli projít dokumentaci k tomu, aby se vyhnuli cyklovým odkazům v iOSu a zajistili tak efektivní využití paměti.

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

Opožděné inicializace se dá použít k odvozování vytvoření objektu, dokud ho nebude poprvé použito. Tato technika se primárně používá ke zlepšení výkonu, zabránění výpočtům a snížení požadavků na paměť.

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

  • Aplikace nemusí objekt používat.
  • Před vytvořením objektu musí být dokončeny další nákladné operace.

Třída Lazy<T> se používá k definování opožděného 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 přístupu k Lazy<T>.Value vlastnosti dochází k opožděné inicializaci. Zabalený typ se vytvoří a vrátí při prvním přístupu a uloží se pro jakýkoli budoucí přístup.

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

Implementace asynchronních operací

.NET poskytuje asynchronní verze mnoha jeho rozhraní API. Na rozdíl od synchronních rozhraní API asynchronní rozhraní API zajišťují, že vlákno aktivního spouštění nikdy neblokuje volající vlákno po určitou dobu. 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 odblokuje vlákno uživatelského rozhraní, které pomůže zlepšit uživatelské prostředí s aplikací.

Kromě toho by se na vlákně na pozadí měly spouštět dlouhotrvající operace, aby se zabránilo blokování vlákna uživatelského rozhraní. .NET poskytuje async klíčová await slova, která umožňují psaní asynchronního kódu, který provádí dlouhotrvající operace na vlákně na pozadí, a přistupuje k výsledkům při dokončení. I když se ale dlouhotrvající operace dají spustit asynchronně pomocí klíčového await slova, nezaručuje se, že se operace spustí na vlákně na pozadí. Místo toho to lze 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()
  {
    ...
  }
}

Metoda RecognizeFace se spustí na vlákně na pozadí s metodou RecognizeFaceButtonClick čekající na RecognizeFace dokončení metody před pokračováním.

Dlouhotrvající operace by také měly podporovat zrušení. Pokud například uživatel přejde v aplikaci, může být pokračování dlouhotrvající operace nepotřebné. Model implementace zrušení je následující:

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

Důležité

Třída CancellationTokenSource implementuje IDisposable rozhraní a proto CancellationTokenSource.Dispose by měla být vyvolána metoda po CancellationTokenSource dokončení instance.

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

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

Spravované jazyky, jako je C#, používají uvolňování paměti k uvolnění paměti přidělené objektům, které se už nepoužívají. Dvě kolektory paměti používané platformou Xamarin jsou:

  • SGen – Jedná se o generační uvolňování paměti a je výchozí uvolňování paměti na platformě Xamarin.
  • Boehm – jedná se o konzervativní, negenerační uvolňování paměti. Jedná se o výchozí uvolňování paměti používané pro aplikace Xamarin.iOS, které používají klasické rozhraní API.

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

  • Dětská škola – To je místo, kde jsou přiděleny nové malé objekty. Když v dětském pokoji dojde místo, dojde k menší uvolňování paměti. Všechny živé objekty se přesunou do hlavní haldy.
  • Hlavní halda – to je místo, kde jsou dlouho běžící objekty zachovány. Pokud v hlavní haldě není dostatek paměti, dojde k významnému uvolňování paměti. Pokud se velké uvolňování paměti nepodaří uvolnit dostatek paměti, nástroj SGen požádá systém o více paměti.
  • Velký prostor objektu – to je místo, kde jsou objekty, které vyžadují více než 8 000 bajtů, se uchovávají. Velké objekty nebudou v dětském pokoji začínat, ale místo toho budou přiděleny v této haldě.

Jednou z výhod SGen je, že doba potřebnou k provedení menší uvolňování paměti je úměrná počtu nových živých objektů vytvořených od poslední menší 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 budou trvat kratší dobu než hlavní uvolňování paměti. Stále dochází k hlavním uvolňování paměti, ale méně často.

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

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

Když SGen spustí uvolňování paměti, zastaví vlákna aplikace, zatímco uvolní paměť. Během uvolnění paměti se aplikace může v uživatelském rozhraní krátce pozastavit nebo zadržovat. Způsob, jakým je toto pozastavení srozumitelné, závisí na dvou faktorech:

  1. Frekvence – jak často dochází k uvolňování paměti. Frekvence uvolňování paměti se zvýší, protože je mezi kolekce přiděleno více paměti.
  2. Doba trvání – jak dlouho bude každá jednotlivá uvolňování paměti trvat. To je zhruba úměrné počtu živých objektů, které se shromažďují.

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

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

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

Zmenšení velikosti 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 zkompilované do jazyka sestavení ARM. Rozhraní .NET Framework je součástí nepoužívaných tříd, které jsou odstraněny pouze v případě, že je povolena příslušná možnost linkeru.
  • Aplikace pro Android jsou kompilované do zprostředkujícího jazyka (IL) a zabalené pomocí monoVM a kompilace JIT (just-in-time). Nepoužívané třídy architektury jsou odstraněny pouze v případě, že je povolena příslušná možnost linkeru.
  • Windows Phone aplikace jsou kompilovány do IL a spouštěné integrovaným modulem runtime.

Kromě toho, pokud aplikace používá rozsáhlé použití obecných typů, konečná velikost spustitelného souboru se dále zvýší, protože bude obsahovat nativně kompilované verze obecných možností.

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

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

Linker options for Xamarin.iOS

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

Linker options for Xamarin.Android

Linker poskytuje tři různá nastavení pro řízení jeho chování:

  • Ne linkovat – Linker neodebere žádné nepoužívané typy a metody. Z důvodů výkonu se jedná o výchozí nastavení pro sestavení ladění.
  • Pouze sady SDK nebo sestavení sady SDK rozhraní Link Framework – Toto nastavení zmenšuje pouze velikost těchto sestavení, která jsou dodávána Xamarinem. Uživatelský kód nebude ovlivněn.
  • Propojit všechna sestavení – Jedná se o agresivnější optimalizaci, která bude cílit na sestavení sady SDK a uživatelský kód. Pro vazby odeberete nepoužitá backingová pole a zpřístupníte každou instanci (nebo vázané objekty) méně paměti.

Propojení všech sestavení by mělo být použito s opatrností, protože může narušit aplikaci neočekávanými způsoby. Statická analýza prováděná linkerem nemusí správně identifikovat veškerý požadovaný kód, což vede k příliš velké míře odebrání kódu z kompilované aplikace. Tato situace se projeví pouze za běhu, když dojde k chybovému ukončení 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 odkazovány, ale jsou vyžadovány aplikací pomocí jednoho z následujících atributů:

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

Může být například nutné zachovat výchozí konstruktory typů, které se dynamicky vytvářejí. Také použití serializace XML může vyžadovat, aby vlastnosti typů byly zachovány.

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

Další techniky redukce velikosti

Existuje celá řada architektur procesoru, které využívají mobilní zařízení. Proto Xamarin.iOS a Xamarin.Android vytvářejí fat binární soubory , 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 je možné použít k dalšímu zmenšení velikosti spustitelného souboru aplikace:

  • Ujistěte se, že je vytvořené sestavení vydané verze.
  • Snižte počet architektur, pro které je aplikace vytvořená, aby se zabránilo vytvoření 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 v každém sestavení (propojení všech projektů pro iOS a propojení všech sestavení pro projekty Android).

Aplikace pro Android je také možné rozdělit na samostatný soubor APK pro každou architekturu ABI ("architektura"). Další informace najdete v tomto blogovém příspěvku: Jak zachovat velikost aplikace pro Android dolů.

Optimalizace prostředků obrázků

Image jsou některé z nejdražších prostředků, které aplikace používají, a často se zaznamenávají ve vysokých rozlišeních. I když to vytváří živé obrázky plné podrobností, 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ého obrázku. Je plýtvání dekódováním obrázku s vysokým rozlišením v paměti, když se zmenší na menší velikost pro zobrazení. Místo toho snižte využití procesoru a nároky na paměť vytvořením více verzí rozlišení uložených imagí, které jsou blízko předpovídané velikosti zobrazení. Například obrázek zobrazený v zobrazení seznamu by měl být pravděpodobně nižší rozlišení než obrázek zobrazený na celé obrazovce. Kromě toho je možné načíst škálované verze obrázků s vysokým rozlišením, aby se efektivně zobrazovaly s minimálním dopadem na paměť. Další informace naleznete v tématu Efektivní načtení 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 na paměť aplikace. Proto by se měly vytvořit pouze v případě potřeby a měly by být uvolněny, jakmile je aplikace už nevyžaduje.

Snížení doby aktivace aplikace

Všechny aplikace mají aktivační období, což je doba mezi spuštěním aplikace a okamžikem, kdy je aplikace připravená k použití. Toto období aktivace poskytuje uživatelům první dojem z aplikace, a proto je důležité zkrátit dobu aktivace a vnímání uživatele, aby získali příznivé první dojem z aplikace.

Před zobrazením počátečního uživatelského rozhraní aplikace by měla poskytnout úvodní obrazovku, která uživateli oznámí, že aplikace začíná. Pokud aplikace nemůže rychle zobrazit své počáteční uživatelské rozhraní, měla by se úvodní obrazovka použít k informování uživatele o průběhu aktivace, aby vám nabídla jistotu, že aplikace nezablokovala. Toto ujišťování může být indikátor průběhu nebo podobný ovládací prvek.

Během období aktivace aplikace spouštějí logiku aktivace, která často zahrnuje načítání a zpracování prostředků. Dobu aktivace je možné snížit tím, že zajistíte, aby se požadované prostředky zabalily do aplikace, a ne aby se načítaly vzdáleně. Za určitých okolností 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, můžou se zástupná data postupně nahradit ze vzdáleného zdroje. Kromě toho by logika aktivace aplikace měla provádět jenom práci, která je nutná k tomu, aby uživatel mohl aplikaci začít používat. To může pomoct při zpoždění načítání dalších sestavení, protože sestavení jsou načtena při 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 zvýšené využití šířky pásma sítě způsobí zvýšené využití baterie zařízení. Uživatelé navíc můžou aplikaci používat v omezeném prostředí šířky 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 z procesu komprese ale může také vést ke zvýšení využití baterie. Proto by měl být tento kompromis pečlivě vyhodnocen před rozhodováním, zda přesunout komprimovaná data přes síť.

Dalším problémem, který je třeba vzít v úvahu, je formát dat, která se přesouvají mezi aplikací a webovou službou. Dva primární formáty jsou JAZYK XML (Extensible Markup Language) 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 znaků formátování. JSON je textový formát výměny dat, který vytváří kompaktní datové části, což vede ke snížení požadavků na šířku pásma při odesílání dat a příjmu dat. Proto je json často upřednostňovaným formátem pro mobilní aplikace.

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

Data načtená z webové služby by se měla ukládat místně do mezipaměti, přičemž data uložená v mezipaměti se využívají místo opakovaného načítání z webové služby. Při přijetí tohoto přístupu by se ale měla implementovat také 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 popisuje techniky zvýšení výkonu aplikací vytvořený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í.