Výkon napříč platformami
Nízký výkon aplikace se prezentuje mnoha způsoby. Aplikace může vypadat jako nereagující, může způsobit pomalé posouvání a může snížit životnost baterie. Optimalizace výkonu ale zahrnuje nejen implementaci efektivního kódu. Je také potřeba zvážit zkušenosti uživatele s výkonem aplikace. Například zajištění toho, aby se operace spouštěly bez blokování uživatele v provádění dalších aktivit, můžou pomoct zlepšit uživatelské prostředí.
Použití profileru
Při vývoji aplikace je důležité se po profilování pouze pokusit o optimalizaci kódu. Profilace je technika pro určení, 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 spuštění kódu, aby bylo možné zjistit nejlepší příležitosti pro optimalizaci.
Xamarin Profiler bude měřit, vyhodnocovat a pomáhat při hledání problémů souvisejících s výkonem v aplikaci. Dá se použít k profilování aplikací Xamarin.iOS a Xamarin.Android z Visual Studio pro Mac nebo sady Visual Studio. Další informace o Xamarin Profiler naleznete v tématu Úvod do Xamarin Profiler.
Při profilaci aplikace se doporučují následující osvědčené postupy:
- Vyhněte se profilaci aplikace v simulátoru, protože simulátor může zkreslit 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í nebudou vždy zobrazovat charakteristiky výkonu jiných zařízení. Profilace by se ale měla provádět minimálně 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 jiné aplikace.
Prostředky IDisposable vydané verze
Rozhraní IDisposable
poskytuje mechanismus pro uvolnění prostředků. Poskytuje metodu Dispose
, která by se měla implementovat pro explicitní uvolnění 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í souborů, datových proudů a síťových připojení.
- Když třída vlastní spravované
IDisposable
prostředky.
Příjemci typu pak mohou volat implementaci IDisposable.Dispose
k volným prostředkům, pokud už instance není nutná. Existují dva přístupy k dosažení tohoto:
- Zabalením objektu
IDisposable
using
do příkazu. - Zabalením volání
IDisposable.Dispose
dotry
/finally
bloku.
Zabalení objektu IDisposable do příkazu using
Následující příklad kódu ukazuje, jak zalomit IDisposable
objekt v using
příkazu:
public void ReadText (string filename)
{
...
string text;
using (StreamReader reader = new StreamReader (filename)) {
text = reader.ReadToEnd ();
}
...
}
StreamReader
Třída implementuje IDisposable
a using
příkaz poskytuje pohodlnou syntaxi, která volá metodu StreamReader.Dispose
StreamReader
objektu před tím, než přejde mimo rozsah. using
V rámci bloku StreamReader
je objekt jen pro čtení a nelze ho znovu přiřadit. Příkaz using
také zajišťuje, že metoda je 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 IDisposable
a 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 se zabránilo nevracení paměti, události by se měly 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í obsahuje tento odkaz, uvolňování paměti paměti odběratele neuvolní.
Následující příklad kódu ukazuje, jak se odhlásit od 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
se odhlásí od události ve své Dispose
metodě.
K referenčním cyklům může také dojít při použití obslužných rutin událostí a syntaxe lambda, protože výrazy lambda mohou 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ých odkazů k zabránění nesmrtelným objektům
Poznámka:
Vývojáři pro iOS by si měli projít dokumentaci týkající se zabránění cyklických odkazů v iOSu , aby jejich aplikace efektivně používaly paměť.
Zpoždění nákladů na vytváření objektů
Opožděné inicializace lze použít k odložit vytvoření objektu, dokud se poprvé nepoužije. 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 se musí dokončit 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)
{
...
}
Opožděné inicializace nastane při prvním přístupu k Lazy<T>.Value
vlastnosti. 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 Opožděná inicializace.
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 aktivní spouštěcí vlákno nikdy nezablokuje volající vlákno po dlouhou 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 dlouhotrvající operace měly spouštět na vlákně na pozadí, aby se zabránilo blokování vlákna uživatelského rozhraní. .NET poskytuje async
klíčová slova, await
která umožňují psaní asynchronního kódu, který provádí dlouhotrvající operace ve vlákně na pozadí, a přistupuje k výsledkům po dokončení. Zatímco dlouhotrvající operace se dají spouštět asynchronně pomocí klíčového await
slova, nezaručí se tím, že operace poběží 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í ve vlákně na pozadí s metodou RecognizeFaceButtonClick
, která čeká, až RecognizeFace
se metoda dokončí, než bude pokračovat.
Dlouhotrvající operace by také měly podporovat zrušení. Například pokračování dlouhotrvající operace může být zbytečné, pokud uživatel přejde v aplikaci. Model implementace zrušení je následující:
- Vytvořte
CancellationTokenSource
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á bude reagovat na zrušení.
- Zavolejte metodu
CancellationTokenSource.Cancel
, která poskytuje oznámení o zrušení.
Důležité
Třída CancellationTokenSource
implementuje IDisposable
rozhraní, a proto CancellationTokenSource.Dispose
by metoda měla být vyvolána po CancellationTokenSource
dokončení instance.
Další informace najdete v tématu Přehled podpory asynchronní podpory.
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ě uvolňování paměti používané platformou Xamarin jsou:
- SGen – Jedná se o generační systém uvolňování paměti a jedná se o výchozí systém uvolňování paměti na platformě Xamarin.
- Boehm – Jedná se o konzervativní, negenerační systém uvolňování paměti. Jedná se o výchozí systém uvolňování paměti používaný pro aplikace Xamarin.iOS, které používají klasické rozhraní API.
SGen využívá jeden ze tří hald 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 prostředí dojde místo, dojde k menšímu uvolňování paměti. Všechny živé objekty se přesunou do hlavní haldy.
- Hlavní halda – zde se uchovávají dlouho běžící objekty. 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, SGen požádá systém o více paměti.
- Velký prostor objektu – to je místo, kde jsou zachovány objekty, které vyžadují více než 8 000 bajtů. Velké objekty nebudou začínat ve školce, 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 se budou vyskytovat hlavní 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 vyšší, 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í sady 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 může aplikace v uživatelském rozhraní krátce pozastavit nebo zadržovat. To, jak je toto pozastavení možné, závisí na dvou faktorech:
- Frekvence – jak často dochází k uvolňování paměti. Frekvence uvolňování paměti se zvýší, protože mezi kolekce je přiděleno více paměti.
- Doba trvání – jak dlouho bude trvat jednotlivé uvolňování paměti. 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ůstal naživu, bude mnoho krátkých uvolňování paměti. Naopak pokud jsou nové objekty přidělovány pomalu a objekty zůstanou aktivní, bude méně, ale déle 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 obzvláš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 najdete v tématu Prostředky IDisposable vydané verze.
- Jakmile už nejsou potřeba, zrušte registraci obslužných rutin událostí, aby byly objekty shromažďovatelné. 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 se předem kompilují do jazyka sestavení ARM. Součástí rozhraní .NET Framework jsou nepoužívané třídy, které se odstraňují pouze v případě, že je povolená příslušná možnost linkeru.
- Aplikace pro Android se kompilují do zprostředkujícího jazyka (IL) a kompilují se pomocí MonoVM a kompilace JIT (just-in-time). Nepoužívané třídy architektury jsou zrušeny pouze v případě, že je povolena příslušná možnost linkeru.
- Aplikace windows Telefon jsou kompilovány do il a spouštěny integrovaným modulem runtime.
Navíc, pokud aplikace používá rozsáhlé obecné typy, 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í, platforma Xamarin zahrnuje linker jako součást nástrojů sestavení. 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 a určí, které typy a členy ve skutečnosti aplikace používá. Potom 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 projektu Xamarin.iOS:
Následující snímek obrazovky ukazuje možnosti linkeru v Visual Studio pro Mac projektu 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 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. U vazeb odeberete nepoužívané záložní pole a zkrátí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ému 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 se vytvoří 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í (propojit vše pro projekty iOS a propojit všechna 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 udržet velikost aplikace pro Android nižší.
Optimalizace prostředků image
Obrázky jsou některé z nejdražších prostředků, které aplikace používají, a často se zaznamenávají ve vysokém rozlišení. 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é image. Při zmenšení velikosti zobrazení je plýtvání dekódováním obrázku s vysokým rozlišením v paměti. Místo toho snižte využití procesoru a využití paměti vytvořením více verzí rozlišení uložených imagí, které jsou blízko předpovězených velikostí zobrazení. Například obrázek zobrazený v zobrazení seznamu by pravděpodobně měl mít 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ázků může zobrazení prostředků obrázků výrazně zvýšit nároky na paměť aplikace. Proto by měly být vytvořeny 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í dobu aktivace, což je doba mezi spuštěním aplikace a okamžikem, kdy je aplikace připravená k použití. Toto období aktivace uživatelům poskytuje první dojem aplikace, takže je důležité zkrátit dobu aktivace a vnímání uživatele, aby získali příznivé první dojem z aplikace.
Než aplikace zobrazí počáteční uživatelské rozhraní, měla by poskytnout úvodní obrazovku, která uživateli oznámí, že se aplikace spouští. Pokud aplikace nemůže rychle zobrazit své počáteční uživatelské rozhraní, úvodní obrazovka by měla být použita k informování uživatele o průběhu během období aktivace, aby nabídla jistotu, že aplikace nezablokovala. Toto zajiště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 se zajistí, aby se požadované prostředky zabalily do aplikace, a ne vzdáleně načítaly. Například za určitých okolností může být vhodné během období 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 se dají 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 v případě, že zpožďuje načítání dalších sestavení, protože se sestavení načtou při prvním použití.
Omezení komunikace webové služby
Připojení 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 přenosem přes síť. Dodatečné využití procesoru z procesu komprese ale může také vést ke zvýšení využití baterie. Proto by tento kompromis měl být pečlivě vyhodnocen před rozhodnutím, zda přesunout komprimovaná data přes síť.
Dalším problémem, který je potřeba vzít v úvahu, je formát dat, která se pohybují 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í 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 přes síť. Pomocí 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 volání provedených aplikací. Obecně platí, že vzdálené volání, které přenáší větší datovou část, trvá podobnou dobu jako volání, které přenáší pouze malou datovou část.
Data načtená z webové služby by se měla ukládat do mezipaměti místně, 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 vhodná strategie ukládání do mezipaměti, aby se aktualizovala data v místní mezipaměti, pokud se změní ve webové službě.
Shrnutí
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í.