Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 3 - Výkon coroutine aplikací v Kotlin

V minulé lekci, Tvorba coroutines v Kotlin, jsme si ukázali jednotlivé způsoby, jak tvořit coroutines a popsali si rozdíly mezi nimi.

Vítám vás u dalšího dílu tutoriálu zaměřeného na Kotlin coroutines, které nám umožňují tvořit asynchronní aplikace. Z minulých lekcí už víme, jak tvořit jednotlivé coroutines. Bohužel jsme si neukázali v praxi, o kolik se nám zvýší jejich výkon, pokud v nich coroutines použijeme. Dnes se tedy zaměříme na výkon coroutine aplikací.

Čas výkonu aplikace

Nejprve začneme porovnáním kódu bez coroutines s asynchronním kódem a v obou budeme sledovat čas potřebný k výkonu programu. Abychom mohli simulovat nějakou časově nákladnou operaci, použijeme v prvním případě Thread.sleep() a pro kód s coroutines delay(). Jako nákladnou operaci si můžeme představit například získání dat z externího zdroje, například z internetu. Tato operace bude v naší ukázce trvat jednu sekundu.

Příklad bez coroutines

Nejdříve si ukážeme kód bez coroutines.

Vytvořme si nový projekt a přidejme do něj potřebné závislosti. Do vygenerované třídy Main nejprve přidáme pomocnou funkci ulozDataNaServer(), která se bude vykonávat jednu sekundu:

fun ulozDataNaServer() {
   Thread.sleep(1000)
}

Abychom mohli měřit, jak dlouho trvá provedení operací, budeme používat blok measureTimeMillis. Ten nám vrátí čas, který byl potřeba na vykonání operací v něm volaných. My v bloku measureTimeMillis dvakrát zavoláme funkci ulozDataNaServer() a budeme tak simulovat dvě operace nahrávání dat na server. Metodu main() upravíme následovně:

fun main() {
   val casVykonavani = measureTimeMillis {
       ulozDataNaServer()
       ulozDataNaServer()
   }

   println(casVykonavani)
}

Poté, co kód spustíme, dostaneme následující výstup:

2000

V příkladech, které budeme uvádět, se může mírně lišit čas vykonávání. Na různých počítačích totiž budou tyto operace vykonány různou rychlostí. Výstup se ale určitě nebude lišit o 500 ms či více.

Výkon coroutines tvořených pomocí launch

V dalším příkladu, který si představíme, použijeme pro tvorbu coroutines blok launch.

Jak víme z minulých lekcí, launch nám slouží k vykonání kódu asynchronně.

Do našeho projektu si přidejme novou pomocnou funkci ulozDataNaServerDelay(), která bude místo Thread.sleep() používat delay(). Abychom v ní mohli používat funkci delay(), budeme ji muset přidat modifikátor suspend. Implementace funkce bude tedy vypadat následovně:

suspend fun ulozDataNaServerDelay() {
   delay(1000)
}

Přesuneme se do metody main() a změníme v ní blok measureTimeMillis. V něm tentokrát vytvoříme pomocí bloku runBlocking a launch dvě coroutines, které zavolají naší druhou funkci ulozDataNaServerDelay():

fun main() {
   val casVykonavani = measureTimeMillis {
       runBlocking {
           launch {
               ulozDataNaServerDelay()
           }

           launch {
               ulozDataNaServerDelay()
           }
       }
   }

   println(casVykonavani)
}

Poté, co tento kód spustíme, dostaneme následující výstup:

1035

Jak vidíme, čas vykonávání se snížil skoro na polovinu. Je to proto, že se oba bloky launch vykonávaly paralelně.

Podobného výsledku bychom dosáhli, kdybychom vytvořili dvě vlákna. Kdybychom však měli například sto tisíc volání funkce ulozDataNaServer() a pro každé tvořili vlastní vlákno, došla by nám paměť. V případě coroutines by nám paměť nedošla, protože jsou mnohonásobně míň náročné na systémové prostředky než vlákna.

Příklady pro získání hodnoty

Pojďme si nyní výkon aplikace porovnat u příkladů, v nichž budeme chtít získat nějaké hodnoty. Představme si například zpracování dat z externího zdroje včetně matematických operací u nich prováděných.

Výkon aplikace bez coroutines

Začneme opět ukázkou bez coroutines. V projektu si vytvoříme dvě funkce bez modifikátoru suspend. Obě opět pouze uspíme pomocí Thread.sleep(), abychom simulovali zpoždění jednu sekundu. Následně vrátíme hodnotu. Funkce jsme pro ilustraci pojmenovali zjistiMedian() a sectiPrvocisla(). Výsledný kód bude vypadat následovně:

fun zjistiMedian(): Int {
   Thread.sleep(1000)
   return 10
}

fun sectiPrvocisla(): Int {
   Thread.sleep(1000)
   return 20
}

Nyní obě funkce zavoláme v metodě main(), kde stejně jako v předchozích příkladech použijeme blok measureTimeMillis. Také pak vypíšeme součet obou hodnot. Kód v main() bude vypadat následovně:

fun main() {
   val casVykonavani = measureTimeMillis {
       val median = zjistiMedian()
       val soucetPrvocisel = sectiPrvocisla()
       println("Výsledek: ${median + soucetPrvocisel}")
   }

   println(casVykonavani)
}

Poté, co tento program spustíme, dostaneme tento výstup:

Výsledek: 30
2000

První řádek je součet návratové hodnoty obou funkcí. Na dalším řádku máme čas vykonání programu. Stejně jako minule, bude vykonávání operací trvat dvě sekundy. Bohužel tento čas je zbytečně dlouhý. Abychom zlepšili výkon aplikace, použijeme coroutines.

Výkon coroutines tvořených pomocí async

Víme již, že pro tvorbu coroutines, které nesou určitou hodnotu, musíme použít blok async. Hodnotu dané async coroutine pak získáme voláním metody await(). Pojďme si tedy vše ukázat na posledním příkladu. Abychom mohli demonstrovat čas získávání hodnot z externího zdroje, použijeme v něm nyní opět funkci delay().

Začneme také přidáním dvou funkcí s modifikátorem suspend a vytvoříme funkce zjistiMedianDelay() a sectiPrvocislaDelay(). Jejich implementace bude podobná jako v dřívějším příkladu s couroutines, navíc pouze doplníme návratovou hodnotu:

suspend fun zjistiMedianDelay(): Int {
   delay(1000)
   return 10
}

suspend fun sectiPrvocislaDelay(): Int {
   delay(1000)
   return 20
}

Nyní můžeme upravit kód v metodě main(). V něm obě metody zavoláme a poté vypíšeme získaný výsledek a celkový čas obou operací:

fun main() {
   val casVykonavani = measureTimeMillis {
       runBlocking {
           val median = async {
               zjistiMedianDelay()
           }

           val soucetPrvocisel = async {
               sectiPrvocislaDelay()
           }

           println("Výsledek: ${median.await() + soucetPrvocisel.await()}")
       }
   }

   println(casVykonavani)
}

Vytvořili jsme dvě coroutines pomocí bloku async, které vypočítají naši hodnotu. V každé z nich jsme zavolali naše funkce uspané na sekundu. Následně jsme vypsali součet a délku běhu aplikace do konzole. Kód spustíme a dostaneme tento výstup:

Výsledek: 30
1030

Snížili jsme výkon aplikace opět skoro na poloviční dobu, protože se kód vykonával asynchronně.

V dalších dílech se podíváme, jak můžeme upravovat vlastnosti coroutines.

V příští lekci, Dispatchers a CoroutineContext v Kotlin, si představíme dispatchers a vysvětlíme si, jak fungují a proč je dobré je v našich aplikacích využít.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 1x (237.64 kB)
Aplikace je včetně zdrojových kódů v jazyce Kotlin

 

Předchozí článek
Tvorba coroutines v Kotlin
Všechny články v sekci
Coroutines v Kotlin
Přeskočit článek
(nedoporučujeme)
Dispatchers a CoroutineContext v Kotlin
Článek pro vás napsal Marek Urbańczyk
Avatar
Uživatelské hodnocení:
4 hlasů
Autor se věnuje programování v Kotlinu, Javě. Má také zkušenosti s C#.
Aktivity