Každá aplikace v mobilním telefonu je omezena velikostí použitelné paměti a rychlostí zpracování bytekódu. MIDP aplikace obvykle používají ke svému běhu od 150 kB do 200 kB paměti heap a velikost vlastní aplikace, tzv. JARu, je řádově 10 kB (30 až 60 kB). Z těchto omezení plyne, že je třeba změnit způsob psaní kódu a začít optimalizovat aplikaci již na úrovní zdrojového kódu. Tento článek ukazuje základní programovací techniky, používané v mobilních zařízeních.

Benchmarking

Abychom mohli otestovat aplikaci na velikost použité paměti a rychlosti kódu, potřebujeme k tomu vhodný prostředek. Pokud se podíváme do J2ME API, najdeme zde dvě třídy Runtime a System. Pro nás jsou ve třídě Runtime zajímavé dvě metody, public long freeMemory() a public long totalMemory().

Metoda freeMemory() vrací velikost volné paměti, kterou máme momentálně k dispozici a metoda totalMemory() velikost celkové paměti. Obě hodnoty jsou v bytech. Pro zjištění, kolik paměti používá daný objekt, můžeme použít tento kód:

Runtime runtime = Runtime.getRuntime(); // získáme Runtime
long before, after; // zde budou uloženy hodnoty volné paměti
System.gc(); // zavoláme Garbage Collector, abychom uvolnili paměť
before = runtime.freeMemory(); // získáme velikost volné paměti před vytvořením objektu
Object o = new Object(); // vytvoříme objekt
after = runtime.freeMemory(); // a opět získáme velikost volné paměti
System.out.print(„Object use “ + (before – after) + „B“); // rozdíl před a po vytvoření objektu „o“

Ve třídě System je důležitá metoda public static long currentTimeMillis(), kterou použijeme pro zjištění, jak náročná je ta část kódu, která nás zajímá. Tato metoda, jak je dobrým zvykem, vrací čas v milisekundách od půlnoci 1. ledna 1970. Pro zjištění, kolik času zabere metoda test(), použijeme tento kód:

long start, finish; // zde budou uloženy hodnoty časů
start = System.currentTimeMillis(); // získej aktuální čas
test(); // proveď metodu test()
finish = System.currentTimeMillis(); // a získej opět aktuální čas
long duration = finish – start; // v duration je trvání metody test()

Typy optimalizace

Po nezbytném úvodu se už budeme zabývat jednotlivými optimalizačními technikami. Většina optimalizací se navzájem doplňuje, ale v základu je můžeme rozdělit na optimalizaci použité paměti, na optimalizaci rychlosti a na optimalizaci velikosti výsledného balíku JAR.

Optimalizace použité paměti

  • Vytváření, uvolňování a opětovné použití objektů – předtím, než objekt vytvoříte, si uvědomte zda je ta část kódu, kde objekt vytváříte, časově kritická. Pokud ano, snažte se objekt vytvořit ještě před kritickou částí, v opačném případě použijte pomalé inicializace.
  • Použití již vytvořených objektů – pokud již máte objekt vytvořen, zvažte jestli není možné jej znovu použít. Vytváření objektů je časově náročné a použití již vytvořeného objektů může ušetřit spoustu času.
  • Pomalá inicializace – pokud to není pro aplikaci kritické, vytvářejte objekty až v případě, že je opravdu potřebujete. Uvědomte si, že můžete držet v paměti něco, co vlastně vůbec nepotřebujete!
  • Vyvarujte se použití výjimek, jak jen to jde – snažte se, aby kód nevyvolával další výjimky než ty, které už v API jsou (I/O operace atd.). Vytvoření výjimky zabírá čas i paměť, neboť se vytváří objekt Exception.
  • String versus StringBuffer – při spojování řetězců použijte StringBuffer místo operátoru ‚+‘. Řetězec se totiž při spojování převede na objekt typu StringBuffer, zavolá metodu append() s příslušným parametrem a výsledek se získá zavoláním toString().

Optimalizace rychlosti

  • Optimalizace smyček – při použití smyčky se vyvarujte opakovanému volání kódu. Například při průchodu vektoru je třeba zjistit délku vektoru. Obvyklou chybou je, že se při každém průchodu volá metoda size(). Řešení je uložit velikost do lokální proměnné a tu použít ve smyčce.
  • Použití polí místo objektů – zvažte, zda není místo objektů Vector a Hashtable možno použít pole. Jsou jednodušší a tím i rychlejší.
  • Uvolňujte zdroje – poté, co již nepotřebujete vytvořené objekty, přiřaďte jím explicitně hodnotu null. Pomůžete tím Garbage Collectoru rozeznat, které objekty již nejsou potřeba.
  • Používejte lokální proměnné místo přímého přístupu – tato optimalizace souvisí s optimalizací smyček. Obecně je přístup k lokální proměnné rychlejší, než práce s globální proměnnou.

Optimalizace velikosti výsledné aplikace

  • Optimalizujte počet tříd – zvažte, zda není možné některé třídy spojit do jedné. Každá prázdná třída zabere přibližně okolo 200 B. Pokud je to možné, snažte se implementovat do jedné třídy model (Model), zobrazení (View) i řízení (Control).
  • Používejte obfuskátory – pro celkové zmenšení výsledné aplikace používejte obfuskátory. Ochrání také vaši aplikaci před dekompilací.
  • Omezte používání rozhraní (Interface) – rozhraní je třída, jejíž definice neobsahuje funkcionalitu. Použíjte jej pouze v případě, že potřebujete vytvořit vícenásobnou implementaci.
  • Nepoužívejte statické inicializátory – každá statická inicializace zabere více paměti, než se může na první pohled zdát. Místo toho použijte inicializaci z externích souborů.

Příklad optimalizace

Uvažujme jednoduchou část kódu, na které si ukážeme jednotlivé optimalizace kódu:

Object[] inputs = new Object[0];
int results = new int[0];
for (int i = 0; i < inputs.length; i++) {
    if (input[i] != null) {
        Process process = new Process(input[i]);
        results[i] = process.calculateResult();
    }
}

  1. Problém: Při každém volání smyčky je zjišťována velikost pole array.
    Řešení: Velikost načteme do lokálním proměnné a tu použijeme ve smyčce.
  2. Problém: Při každém průchodu smyčkou je vytvořen objekt třídy Process, je vypočítán výsledek a pak je objekt uvolněn.
    Řešení: Vytvoříme lokální proměnnou a do ní uložíme odkaz na objekt třídy Process.
  3. Problém: Opakovaně přistupujeme přímo k prvku pole.
    Řešení: Prvek načteme do lokální proměnné a pak s ním teprve pracujeme.
  4. Problém: Vytvořené objekty již nejsou potřeba.
    Řešení: Přiřadíme nepotřebným objektům hodnotu null.

Object[] inputs = new Object[0];
int results = new int[0];
int length = inputs.length;
int results = new int[0]; // řešení problému č. 1
Process process = new Process(); // řešení problému č. 2
Object value; // řešení problému č. 3
for (int i = 0; i < length; i++) {
    value = input[i];
    if (value != null) {
        process.setValue(value);
        results[i] = process.calculateResult();
    }
}
// free unused objects
inputs = null; // řešení problému č. 4
process = null;
value = null;

Pozn. aut.: Tento článek zdaleka neobsahuje všechny možné způsoby optimalizace, ale pouze předkládá základní programovací techniky používané při vývoji J2ME aplikací.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět