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