To samozrejme muzete, treba predanim pozadovane velikosti pri konstrukci seznamu (nebo to take delaji metody typu asList() apod). Mel jsem na mysli spis situace, kdy programator nemuze vedet (nebo se mu to nechce slozite zjistovat), kolik dat vlastne dostane a pouzije konstruktor typu new ArrayList().
Bohuzel jste nepochopil podstatu problemu. Mergovat stringy v cyklu neni spatne kvuli realokaci samotne, ale kvuli tomu, ze se musi pokazde vsechna data stale dokola kopirovat.
Rekneme, ze budeme pridavat cisla od 1000 do 9999. Celkovy pocet zkopirovanych znaku pak bude 4x9000x9000/2. To je docela hodne, ze?
StringBuffer/Builder pravuje podobne jako ArrayList. Sice musi taky prealokovat, ale alokuje o blocich vzdy dvojnasobku predchozi delky. Diky tomuto triku se dostava na linearni (amortizovanou) slozitost - 4x9000x2.
Takze odpoved na puvodni otazku je "ano". :-)
Ja jsem odpovidal "pssp" kde jsme se bavili o ArrayListu vs. LinkedListu, toto vlakno prece vubec neni o konkatenaci Stringu ne (tam ke kopirovani samozrejme dochazi, coz je druhy duvod proc jejich konkatenaci ve velkych smyckach nepouzivat)?
Jen pro upresneni - v soucasnosti se pri realokaci nova delka ArrayListu pocita nasledovne:
newCapacity=(oldCapacity*3)/2+1;
U StringBufferu/StringBuilderu je to presne tak jak pisete:
nova kapacita=(stara_kapacita+1)*2
Podle Stephana T. Lavaveje z Microsoftu, který dělá na STL knihovně pro C++ je vhodný násobek pro vector<T> právě 1.5 prý jim to vyšlo nejlépe. http://channel9.msdn.com/Tags/stl
Kdysi jsem někde viděl nějaký benchmark ArrayListu<T>, který se zaměřil na rychlost závislou na počáteční kapacitě. Výsledkem bylo, že je vhodné ArrayList<T> inicializovat "kulatou" hodnotou = mocninou 2. Já často používám 8 nebo 64, samozřejmě jinou hodnotu, pokud zhruba vím, kolik prvků budu potřebovat.
Další volba pro "nafukovací pole" je Vector<T>, od ArrayListu<T> se liší tuším synchronizací a možností zvolit si "koeficient nafukování". Ale nevím to jistě, zájemci ať si přečtou dokumentaci nebo se podívají do implementace. Jo a ohledně rychlosti: čisté pole T[] bude vždy nejrychlejší.
Několikrát jsem omylem při výpisu nebo spojování Stringů napsal toto: System.out.println(str + i + ' ' + str2....); a nestačil jsem se divit kde mám mezeru, dokud jsem si neuvědomil, jak se provede i + ' ' -> int + char = int.
Ok, reagoval jsem na celou diskuzi (vcetne Vasi prvni odpovedi) a tak trochu i na clanek, kde ten duvod, proc je StringBuffer radove rychlejsi, uplne chybi.
Fascinuje me Vas vhled do Javy vcetne prekladu ap., ale trosku mi chybi nadhled. Tady treba zduvodneni, proc je jedna cesta lepsi, jinde je to pochopeni duvodu, proc je Java dosud tak jednoducha a nema moc tech cool scriptovacich vlastnosti (pochopite, kdyz musite navazovat na praci studentu nebo Indu).
Vcera jsem to zapomne zminit, tak to napravim - tyhle clanky jsou velkym prinosem a vzdycky se neco priucim.
Aha, tak to se omlouvam - ja jsem byl z prvniho prispevku teto vetve "naladeny" na druhou kapitolu clanku, kde se prozatim moc o String* nehovorilo, takze v pohode.
S temi novymi vlastnostmi je to, jak pisete, dvousecna zbran, to je pravda - na jednu stranu je mozne v "mocnejsim" jazyku vic prasit (viz napriklad toblibene IOCCC a to je jeste cecko velmi jednoduchy jazyk ;-), na stranu druhou Java kvuli kratsimu "feature listu" ztraci treba oproti C#, takze ho Sun chtel dohnat a predehnat ;-)
Protoze v pripade, ze by Java setrvala na miste, doslo by k jeji cobolizaci (btw dekuji Lukasovi F., ze me na tento vystizny termin upozornil ;-)
Nakonec vime jak Sun dopadl a dusledkem je zpozdeni JDK7 a navic posunuti nekterych novych vlastnosti do JDK8 a vys.
Duvod byl jediny - budu na tento priklad navazovat podrobneji priste (vcetne profilingu) a tam se rozdily, i kdyz nijak zasadni projevi. Ty rozdily jsou pouze "konstantni", tj. StringBuilder je rychlejsi o x-nasobek (jak kdy, dejme tomu v rozmezi 1,0 az 1,2), kdezto u konkatenace Stringu ten casovy rozdil roste prakticky exponencialne.
Cekal jsem trosku neco jineho, ale clanek paradni a tesim se na dalsi dil. Cekal jsem tipy a triky s jconsoli nebo podobnymi nastroji, hledani slabych mist v aplikaci apod. Srovnani vykonu pri pouziti Large Pages (v linuxu Hugepages). Srovnani vykonu ve virtualizovanem/nevirtualizovanem prostredi. Dik za clanek
K tomu se jeste dostaneme - myslim ted predevsim nastaveni heapu, povoleni large pages, volba GC, poctu vlaken alokovanych GC atd.
Me se napriklad v jedne benchmarkove aplikaci osvedcily tyto volby (pro osmijadro):
-XX:+UseParallelOldGC -XX:ParallelGCThreads=8 -XX:+AggressiveOpts
Ale pro jinou benchmarkovou aplikaci na tom samem stroji jsou tyto volby naopak horsi nez defaultni nastaveni ;-) A pro GUI aplikace, kde by nemelo byt patrne zpozdeni kvuli GC jsem zase zvolil G1, i kdyz ho Oracle prozatim povazuje za experimentalni.
Diky za clanek, tesim se na dalsi pokracovani.
Kdyz si ctu doporuceni v ruznych diskuzich ohledne System.gc()
tak se casto doctu ze System.gc() je evil a kdo ho pouziva tak
jenom proto protoze nekde udelal neco blbe co gc zabranuje
byt "dostatcne optimalni", hard core reseni je zakazat System.gc()
uplne.
Nicmene, co me prekvapilo kdysi bylo ze System.gc() tedy explictini
vyvoalni "full gc", ve skutecnosti nikde negarantuje smazani skutecne vsech
nepotrebnych objektu tj. ze kdyz mam hodne "mrtvych obejktu" na heape
a spustim v iteraci (naprikald 10 cyklu) System.gc() tak dosahnu toho
ze mi gc uvolni vic pameti (nez kdybych zavolal System.gc() jednou).
Coz je docela zajiamvy protoze to oznaceni "full gc" mi evokuje ze se
proleze cela struktura (at uz je to cokoliv) uz pri prvnim zavolani
System.gc().
Jinak to tveakovani parametru a vyber ruznych gc je taky sranda. Ja jsem
naprikald vyresil OutofMemory pouze tim ze jsem nastavil vetsi velikost
"eden generation" oblasti u doufalt gc collectoru, coz byl pro onu tridu
poctace "jenoduchy" MarkSweepCompact gc.
Myslim, ze "full gc" neni full prave proto, ze se nasla spousta matlalu, kteri to z ruznych obskurnich duvodu volaji vsude mozne a pak je nasledkem jen to, ze se aplikace zpomali, protoze full gc trva dlouho. Tak vetsinou dostanou jen procisteni edenu a jsou spokojeni a pritom to program tolik nezpomali. Takovy ten kompromis kdyz se stretnou idealy s praxi.
Kdysi davno se starym redsysem to fungovalo, ale s novym ne, protoze ten je chytrejsi nez autori clanku ;-), takze upravuje vkladane HTML.
Dokonce je nutne rucne pridavat na prazdne radky v (pre) alespon nedelitelnou mezeru (nbsp), jinak se tento radek "optimalizuje" na nic ;-)
Ale priste muzu vypisy dodat jeste ve forme priloh, na ty se nesaha.
Dekuji za clanek a chci se zeptat
- u druheho finalizacniho prikladu se v kazdem cyklu uvolnuje 200K->165K. Dokazal by nekdo vysvetlit, kde se bere ten narust na 200K? Jsou to nejake prazdne stranky nebo cache nebo co?
- na nejake prednasce jsem videl, ze ConcatTest1 se pri pouziti java -server prevede na pouziti builderu tak jak je to v ConcatTest3. Bude o tom autor v pristich dilech mluvit? Pripada mi jako vhodny pristup psat tupy kod, ktery ale serveru chutna a automaticky se zoptimalizuje, nez se snazit o adhoc optimalizace a napachat tim vic skod nez uzitku.
ovšem ke skrytému vytvoření nového objektu dojít ve skutečnosti může a skutečně i několikrát dojde – přijdete na to, kde a proč?
Asi tady
str.append(i);
str.append(' ');
a tady
return str.toString();
V prvním případě kvůli občasné realokaci velikostí již nedostačujícího StringBufferu str
(alokace nového bufferu, kopie z původního místa, úprava referencí, uvolnění původního objektu), v druhém jednorázová konverze StringBuffer -> String.
Ano presne - k realokacim StringBufferu|Builderu bude dochazet ve smycce, protoze puvodni velikost je pro sestnact znaku.
A str.toString() udela defenzivni kopii retezce, aby se pole znaku StringBufferu|Builderu nesdilelo - to je dulezite, protoze kdyby se vratilo interni pole StringBufferu|Builderu jako podle specifikace nemenny retezec, mohl by nekdo zmenou ve StringBufferu|Builderu dosahnou i zmeny v "nemenitelnem" Stringu ;-)
Heh! No tak ted si to vsem natrel! Nebo ti prijde adekvatni obsah clanku a tve buzzwordy future verzi HotSpotu?
Nicmene k clanku. Metoda Object.finalize() a System.gc() by mely byt pouzity jen v neuveritelne extremne krajnich pripadech. Ja jsem snad za deset let praxe nenapsal jediny finalizer a co se pamatuju, ani v cizim kodu jsem na zadny nenarazil. Snadneji v nem vyrobite memory leak rereferenci, nez ze by byl k necemu uzitecny.
Ono pri jednoduchych zretazeniach je zbytocne pouzivat StringBuilder. Samotny kompilator vytvori StringBuilder, zretazi stringy a nakoniec do vysledneho stringu vysledok ulozi, tak ako by to urobili programatori. Ine to je pri pouzivani zretazenia v cykle. Tiez to sice kompilator zmeni, ale je rozdiel, ci vznikne jeden StringBuilder na zaciatku cyklu, alebo stale vznikaju nove StringBuildery vnutri cyklu.