Három különböző megközelítésben generálunk szövegeket és töltjük bele ezeket generikus listába. Olyan környezetet építünk, amely képes tesztelni/mérni a Java program/környezet memória használatát előtte/utána. A tárigény (memória, háttértár, feldolgozandó adatok mennyisége, adatforgalom) fontos eleme a hatékonyságnak. Gyakran előfordul, hogy a tárigényt csökkenteni kell egy program tervezése, implementációja vagy továbbfejlesztése során.
Feladat
A Java program előállít 100000 db 20 hosszúságú szöveget véletlen kiválasztott angol ábécé-beli betűkből. A szövegeket generikus listába tölti be. A program méri az általa felhasznált memória méretét/nagyságát úgy, hogy a műveletek előtti és utáni eredményekből különbséget számol. Egyszerű következtetéssel, becsléssel nagy mennyiségű adatra/memóriaterületre számíthatunk, így érdemes megabájt mértékegységet használni.
Közös konstansok
-
final int MIN_LIMIT=(int)'a';
97, a kis ‘a’ betű, a „legkisebb” generálható véletlen betű/karakter ASCII kódja -
final int MAX_LIMIT=(int)'z';
122, hasonlóan a felső korlát -
final int STRING_LENGTH_LIMIT=20;
a véletlenszerűen generálandó szövegek hossza -
final int STRING_COUNT=1000000;
a véletlenszerűen generálandó szövegek száma, amik generikus listába kerülnek
Három különböző módszer
-
method1():
Az első módszer esetén String típusú szövegobjektumok keletkeznek úgy, hogy karakterenként kerülnek összefűzésre (konkatenáció) a += művelettel (operátorral). A véletlen karakterek sorszámaira int2char típuskényszerítés szükséges explicit módon (char). A vezérlés egymásba ágyazott ciklusokkal történik. -
method2():
A második módszer esetén StringBuilder típusú szövegobjektumok keletkeznek. Szintén karakterenként generálva. Összefűzésüket az append() metódus végzi el. A típuskényszerítés és a vezérlés megegyezik az előző módszerrel. -
method3() :
A harmadik módszer során a StringBuilder típusú szövegobjektumokból a Stream API beépített funkcionális műveletei állítják elő a generikus listát. A vezérlés egyetlen ciklusból áll.
Java forráskódok
Íme az első módszert megvalósító metódus. Futtatása 620 MB memóriát igényel:
1 2 3 4 5 6 7 8 9 10 11 |
private void method1() { List<String> stringList=new ArrayList<>(); for(int i=1; i<=STRING_COUNT; i++) { String generatedString=""; for(int j=1; j<=STRING_LENGTH_LIMIT; j++) generatedString+= (char)(int)(Math.random()*(MAX_LIMIT-MIN_LIMIT+1)+MIN_LIMIT); stringList.add(generatedString); } //System.out.println(stringList); } |
Ez a második módszer metódusa, amely futása során 235 MB memóriát használ:
1 2 3 4 5 6 7 8 9 10 11 |
private void method2() { List<String> stringList=new ArrayList<>(); for(int i=1; i<=STRING_COUNT; i++) { StringBuilder generatedString=new StringBuilder(); for(int j=1; j<=STRING_LENGTH_LIMIT; j++) generatedString.append( (char)(int)(Math.random()*(MAX_LIMIT-MIN_LIMIT+1)+MIN_LIMIT)); stringList.add(generatedString.toString()); } //System.out.println(stringList); } |
A harmadik módszer metódusa. 494 MB „memóriaterületbe kerül” lefuttatni:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private void method3() { Random random=new Random(); List<String> stringList=new ArrayList<>(); for(int i=1; i<=STRING_COUNT; i++) { String generatedString=random.ints(MIN_LIMIT, MAX_LIMIT+1) .limit(STRING_LENGTH_LIMIT) .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) .toString(); stringList.add(generatedString); } //System.out.println(stringList); } |
A metódusok hívása
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
private long getCurrentlyUsedMemory() { long totalMemory=Runtime.getRuntime().totalMemory(); long freeMemory=Runtime.getRuntime().freeMemory(); System.gc(); return (totalMemory-freeMemory)/1048576; } void run() { System.out.println("Felhasznált memória, adatok betöltése..."); System.out.println("1. teszt:"); long memoryStart=getCurrentlyUsedMemory(); System.out.println(" előtt: "+memoryStart+" MB"); method1(); long memoryStop=getCurrentlyUsedMemory(); System.out.println(" után: "+memoryStop+" MB"); System.out.println(" különbség: "+(memoryStop-memoryStart)+" MB"); //... } |
Eredmények
A tesztkörnyezet az alábbi eredményeket írta ki konzolosan:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Felhasznált memória, adatok betöltése... 1. teszt: előtt: 2 MB után: 622 MB különbség: 620 MB 2. teszt: előtt: 16 MB után: 251 MB különbség: 235 MB 3. teszt: előtt: 17 MB után: 511 MB különbség: 494 MB |
Más nézőpontok
- Az is mérhető, hogy a program futása közben hány darab objektum keletkezik, melyik mennyi ideig „él”, melyik mennyi helyet foglal. Ez részletesebb képet nyújthat, összevetve a fenti globális helyfoglalással.
- Figyelhető, hogy az automatikus szemétgyűjtő (Garbage Collector) milyen gyakran fut. Némileg befolyásolható, paraméterezhető a tevékenysége.
- A forráskód bonyolultsága összefüggésben van annak karbantarthatóságával. Fontos lehet, hogy milyen könnyen dokumentálható, továbbfejleszthető.
- A memóriafogyasztás szempontjából „túloptimalizált” program verzióváltás(ok) esetén több tesztelést, módosítást igényelhet. Ha egyáltalán valaki hozzá mer nyúlni. 😉
- A memóriahasználat mérése során figyelembe kell venni, hogy a Java programon kívül Java futtatókörnyezet (JRE) is működik az operációs rendszeren, aminek szintén van erőforrásigénye.
A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.
Bemutattunk többféle módszert, illetve teszteredményeket. Részletes magyarázatot/indoklást most nem adunk a szakmai blogban. A Java SE szoftverfejlesztő tanfolyam szakmai moduljának 17-28. óra Objektumorientált programozás alkalmain természetesen széles körű áttekintést adunk a témakörrel kapcsolatosan, valamint más módszereket is bemutatunk. A hatékonyság klasszikus dimenziói: időigény, tárigény, bonyolultság. Több esettanulmányunk is van, amely egy-egy algoritmus lépésszámát méri, memória- és/vagy sávszélesség igényt mér, illetve elvi és/vagy koncepcionális bonyolultságot próbál meghatározni. Ezek közül többször redukálunk, csökkentünk tipikusan lépésszámot, memóriaigényt.
Átnéztem az ILIAS-on lévő V3TesztKeretrendszer lehetőségeit. Az ősosztályból nem tudtam implementálni az időmérést. Ehhez szeretnék segítséget kérni Sándor.
Ezeket az absztrakt metódusokat névtelen belső osztályban örökítve tudod megírni/felülírni. A V5-nél találsz rá megoldást, ami a dinamikusan lefoglalt tárhely nagyságát méri. A hétvégi órán megbeszélhetjük, ha szeretnéd Ferenc.
Rendben, köszönöm.