IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

3. diel - Výkon coroutine aplikácií v Kotlin

V minulej lekcii, Tvorba Coroutines v Kotline , sme si ukázali jednotlivé spôsoby, ako tvoriť coroutines a popísali si rozdiely medzi nimi.

Vítam vás pri ďalšom diele tutoriálu zameraného na Kotlin coroutines, ktoré nám umožňujú tvoriť asynchrónne aplikácie. Z minulých lekcií už vieme, ako tvoriť jednotlivé coroutines. Bohužiaľ som si neukázali v praxi, o koľko sa nám zvýši ich výkon, pokiaľ v nich coroutines použijeme. Dnes sa teda zameriame na výkon coroutine aplikácií.

Čas výkonu aplikácie

Najprv začneme porovnaním kódu bez coroutines s asynchrónnym kódom av oboch budeme sledovať čas potrebný na výkon programu. Aby sme mohli simulovať nejakú časovo nákladnú operáciu, použijeme v prvom prípade Thread.sleep() a pre kód s coroutines delay(). Ako nákladnú operáciu si môžeme predstaviť napríklad získanie dát z externého zdroja, napríklad z internetu. Táto operácia bude v našej ukážke trvať jednu sekundu.

Príklad bez coroutines

Najprv si ukážeme kód bez coroutines.

Vytvorme si nový projekt a pridajme doň potrebné závislosti. Do vygenerovanej triedy Main najskôr pridáme metódu ulozDataNaServer(), ktorá sa bude vykonávať jednu sekundu:

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

Aby sme mohli merať, ako dlho trvá vykonanie operácií, budeme používať blok measureTimeMillis. Ten nám vráti čas, ktorý bol potrebný na vykonanie operácií v ňom volaných. My v bloku measureTimeMillis dvakrát zavoláme metódu ulozDataNaServer() a budeme tak simulovať dve operácie nahrávania dát na server. Metódu main() upravíme nasledovne:

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

   println(casVykonavani)
}

Potom, čo kód spustíme, dostaneme nasledovný výstup:

2000

V príkladoch, ktoré budeme uvádzať, sa môže mierne líšiť čas vykonávania. Na rôznych počítačoch totiž budú tieto operácie vykonané rôznou rýchlosťou. Výstup sa ale určite nebude líšiť o 500 ms alebo viac.

Výkon coroutines tvorených pomocou launch

V ďalšom príklade, ktorý si predstavíme, použijeme na tvorbu coroutines blok launch.

Ako vieme z minulých lekcií, launch nám slúži na vykonanie kódu asynchrónne.

Do nášho projektu si pridajme novú metódu ulozDataNaServerDelay(), ktorá bude namiesto Thread.sleep() používať delay(). Aby sme v nej mohli používať funkciu delay(), budeme ju musieť pridať modifikátor suspend. Implementácia metódy bude teda vyzerať nasledovne:

suspend fun ulozDataNaServerDelay() {
   delay(1000)
}

Presunieme sa do metódy main() a zmeníme v nej blok measureTimeMillis. V ňom tentokrát vytvoríme pomocou bloku runBlocking a launch dve coroutines, ktoré zavolajú našu metódu ulozDataNaServerDelay():

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

           launch {
               ulozDataNaServerDelay()
           }
       }
   }

   println(casVykonavani)
}

Potom, čo tento kód spustíme, dostaneme nasledujúci výstup:

1035

Ako vidíme, čas vykonávania sa znížil skoro na polovicu. Je to preto, že sa oba bloky launch vykonávali paralelne.

Podobný výsledok by sme dosiahli, keby sme vytvorili dve vlákna. Keby sme ale mali napríklad stotisíc volaní metódy ulozDataNaServer() a pre každé tvorili vlastné vlákno, došla by nám pamäť. V prípade coroutines by nám pamäť nedošla, pretože sú mnohonásobne menej náročné na systémové prostriedky ako vlákna.

Príklady pre získanie hodnoty

Poďme si teraz výkon aplikácie porovnať pri príkladoch, v ktorých budeme chcieť získať nejaké hodnoty. Predstavme si napríklad spracovanie dát z externého zdroja vrátane matematických operácií u nich vykonávaných.

Výkon aplikácie bez coroutines

Začneme opäť ukážkou bez coroutines. V projekte si vytvoríme dve metódy bez modifikátora suspend. Obe opäť iba uspíme pomocou Thread.sleep(), aby sme simulovali oneskorenie jednu sekundu. Následne vrátime hodnotu. Metódy sme pre ilustráciu pomenovali zjistiMedian() a sectiPrvocisla(). Výsledný kód bude vyzerať nasledovne:

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

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

Teraz obe metódy zavoláme v metóde main(), kde rovnako ako v predchádzajúcich príkladoch použijeme blok measureTimeMillis. Tiež potom vypíšeme súčet oboch hodnôt. Kód v main() bude vyzerať nasledovne:

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

   println(casVykonavani)
}

Potom, čo tento program spustíme, dostaneme tento výstup:

Výsledek: 30
2000

Prvý riadok je súčet návratovej hodnoty metód. Na ďalšom riadku máme čas vykonania programu. Rovnako ako minule bude vykonávanie operácií trvať dve sekundy. Bohužiaľ, tento čas je zbytočne dlhý. Aby sme zlepšili výkon aplikácie, použijeme coroutines.

Výkon coroutines tvorených pomocou async

Vieme už, že na tvorbu coroutines, ktoré nesú určitú hodnotu, musíme použiť blok async. Hodnotu danej async coroutine potom získame volaním metódy await(). Poďme si teda všetko ukázať na poslednom príklade. Aby sme mohli demonštrovať čas získavania hodnôt z externého zdroja, použijeme v ňom teraz opäť funkciu delay().

Začneme tiež pridaním dvoch metód s modifikátorom suspend a vytvoríme metódu zjistiMedianDelay() a sectiPrvocislaDelay(). Ich implementácia bude podobná ako v skoršom príklade s couroutines, navyše iba doplníme návratovú hodnotu:

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

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

Teraz môžeme upraviť kód v metóde main(). V ňom obe metódy zavoláme a potom vypíšeme získaný výsledok a celkový čas oboch operácií:

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

           val soucetPrvocisel = async {
               sectiPrvocislaDelay()
           }

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

   println(casVykonavani)
}

Vytvorili sme dve coroutines pomocou bloku async, ktoré vypočítajú našu hodnotu. V každej z nich sme zavolali naše metódy uspané na sekundu. Následne sme vypísali súčet a dĺžku behu aplikácie do konzoly. Kód spustíme a dostaneme tento výstup:

Výsledek: 30
1030

Znížili sme výkon aplikácie opäť skoro na polovičnú dobu, pretože sa kód vykonával asynchrónne.

V ďalších dieloch sa pozrieme, ako môžeme upravovať vlastnosti coroutines.

V budúcej lekcii, Dispatchers a CoroutineContext v Kotline , si predstavíme dispatchers a vysvetlíme si, ako fungujú a prečo je dobré ich v našich aplikáciách využiť.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 1x (237.64 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Kotlin

 

Predchádzajúci článok
Tvorba Coroutines v Kotline
Všetky články v sekcii
Coroutines v Kotline
Preskočiť článok
(neodporúčame)
Dispatchers a CoroutineContext v Kotline
Článok pre vás napísal Marek Urbańczyk
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje programování v Kotlinu, Javě. Má také zkušenosti s C#.
Aktivity