Sziasztok,
a kerdes fokent Javaval es C#-val kapcsolatos, de altalanos kerdeskent sem erdektelen szamomra.
tegyuk fel, hogy van egy ilyenunk:
if(foo.getbar() < limit1 || foo.getBar() > limit to) {
//do something
}
aminek van egy ilyen alternativaja:
int bar = foo.getBar;
if(bar < limit1 || bar > limit to) {
//do something
}
Az elso esetben ketszer hivtunk meg egy fuggvenyt amit eleg lett volna egyszer is. A masodikban deklaraltunk egy valtozot amire nem feltetlenul lett volna szukseg. 1-1.
Feltetelezem az nem tortenik meg, hogy a compiler az egyik alternativat (barmelyiket is) hatterben a masikka optimalizalja.
Elso ranezesre a masodik inkabb tunik premature optimization-nek, mint az elso, tehat az elso alternativa kene, hogy jelentse a best practice-t ebben az esetben.
Helyesek a feltetelezeseim? Tudom nagyon szorszalhasogato tuok lenni ilyen kerdesekben, de ugy vagyok vele, hogy eleg egyszer megkapni ra a valaszt es onnantol beepul a stilusomba.
- 6279 megtekintés
Hozzászólások
Én írnék egy inRange(value, from, to) metódust és azt használnám az if-ben. Így nem kell kétszer getBar()-t hívni és lokális változó sem kell. Plussz írható rá teszt meg újra is hasznosítható (szerintem még olvashatóbb is). És az is lehet, hogy már van is ilyen.
Hogy a kérdésedre is válaszoljak: szerintem a compiler nem tudja az elsőt a másodikba optimalizálni max akkor ha a foo.bar immutable (multi threading nem gond) és a getBar kellően egyszerű, side effect mentes (tehát mondjuk csak azt az immutable foo.bar-t adja vissza).
- A hozzászóláshoz be kell jelentkezni
Örök dilemma, hogy gyorsabban akarod, vagy kevesebb memóriával - ez egy jó példa arra, hogy a két cél miért és hogyan küzd egymás ellen. A kivétel, mint a fehér holló.
- A hozzászóláshoz be kell jelentkezni
Itt nagyon nem mindegy pl. hogy az kifejezés egy ciklusban hajtódik-e végre, és milyen gyakran a program futása során. Az sem mellékes, hogy a getBar metódus csak egy sima getter, vagy van egyéb benne logika?
Java esetén a compiler (javac) nem optimalizál úgy mint egy C++ compiler, hanem a HotSpot végez futásidejű optimalizációt, ami parancssorbból paraméterezhető. Az inline-olás és egyéb optimalizációkat itt hajtja végre.
Én azt az elvet követem, hogy a kód legyen helyes, olvasható. Később én vagy más egyből lássa át, ne legyenek unorthodox megoldások. Ha később kiderülne, hogy egy kódrész problémás teljesítmény szempontjából, akkor lehet kézzel változtatni, de még nem láttam olyat, hogy egy Java program ilyenek miatt lenne lassú. Sokkal inkább egy premature optimization tud elrontani dolgokat. Sokkal többet ér a karbantarthatóság és átláthatóság hosszú távon, mint az a pár nanosec, amit ezen nyersz.
- A hozzászóláshoz be kell jelentkezni
A két megoldás nem ekvivalens a getBar metódus lehetséges mellékhatása miatt (a kisbetű-nagybetű eltérés gondolom csak gépelési hiba). Ha ez nincs, akkor ekvivalens.
A objektum-metódushívás eléggé költséges művelet - arányaiban - a menedzselt nyelvekben, főleg akkor, ha nem final metódust hívunk. Tehát ha mikro-optimalizálni akarsz/kell, akkor az első (igazából sokadik, de itt első :-) lépés, hogy a gyakran hívott metódusokat final-ra állítjuk. Az optimalizálás pedig ennek a kódnak a futásidejére nézve arányaiban sokat jelenthet (közel 50%-ot is szerintem).
Az első esetben a metódushívás kétszer le fog futni, mivel a lehetséges mellékhatást a fordító nem tudja kizárni. (Persze csak abban az esetben, ha az első feltétel igaz.) Amennyiben a metódushívás "final" - azaz nem felüldefiniálható - metódusra történik, akkor inline-osítható a metódushívás, ami sokkal gyorsabb, mintha nem. Plusz ebben az esetben elvben a fordító rájöhet, hogy a hívás mellékhatásmentes, és akkor kioptimalizálhatja a második hívást.
A második esetben a metódushívás mindenképpen csak egyszer fut le. Az érték tárolása új lokális változóba nem lesz külön művelet futásidőben. Ugyanabba a regiszterbe fogja betenni a gépi kód a visszatérési értéket, mint amibe egyébként is tenné a feltételvizsgálathoz. Sőt, eleve regiszterben kapja vissza a metódushívásból (IMHO modern Java és C# így működik), tehát a változónév csak egy fordításidőben létező alias egy már eleve eltárolt értékre. Tehát a második eset mindenképpen gyorsabb lesz. (Vagy maximum egyforma, ha a fenti optimalizációkat mindet meg tudja csinálni a fordító az első esetre.)
Persze azt is nézni kell, hogy a feltételvizsgálat milyen statisztika szerint lesz igaz. Ha az első feltétel igaz a leggyakrabban, akkor ennek az optimalizálásnak nincs értelme.
Egyébként csatlakozom a többiekhez: az optimalizálásnak csak akkor van értelme, ha kimutathatóan ez a rész a szűk keresztmetszet a programodban. Az olvashatóság rovására pedig csak akkor szabad optimalizálni, ha elkerülhetetlen. IMHO a második változat olvashatóbb is, és kisebb a belső redundanciája. Azaz ha valaki belejavít (pl getBar->getFoo javítás), kisebb az esélye, hogy az egyik get hívást átírja, a másikat meg úgyfelejti. A szőrszálhasogatás a kedvenc elfoglaltságom :-).
- A hozzászóláshoz be kell jelentkezni
"Amennyiben a metódushívás "final" - azaz nem felüldefiniálható - metódusra történik, akkor inline-osítható a metódushívás, ami sokkal gyorsabb, mintha nem."
Próbáltam ennek utánaolvasni, és a stack overflowon azt írják, hogy az új JVM simán leoptimalizálja, tehát nem lesz a kettő között különbség.
- A hozzászóláshoz be kell jelentkezni
Egyébként csatlakozom a többiekhez: az optimalizálásnak csak akkor van értelme, ha kimutathatóan ez a rész a szűk keresztmetszet a programodban. Az olvashatóság rovására pedig csak akkor szabad optimalizálni, ha elkerülhetetlen. IMHO a második változat olvashatóbb is, és kisebb a belső redundanciája. Azaz ha valaki belejavít (pl getBar->getFoo javítás), kisebb az esélye, hogy az egyik get hívást átírja, a másikat meg úgyfelejti. A szőrszálhasogatás a kedvenc elfoglaltságom :-).
+1, a kétszeri függvényhívás szembemegye a DRY-al, szerintem is tisztább az akár hosszabb kód.
BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)
- A hozzászóláshoz be kell jelentkezni
Gondolkodtam egy kicsit tegnap óta. Ha kétszer egymás után hívjuk meg ugyanazt a virtuális metódust, akkor a második esetet már lehet optimalizálni akkor is, ha valódi virtuális hívásról van szó, mivel a táblázatokban turkálást nem kell kétszer végigcsinálni, hiszen a referencia típusa nem változhat meg. Szóval az közel 50%-os tippemet lejjeb viszem 10-20-30% körülire :-).
- A hozzászóláshoz be kell jelentkezni
"Az első esetben a metódushívás kétszer le fog futni"
Ez mondjuk nem igaz a lazy eval miatt. Amennyiben az elso feltetel igaz, akkor nem fog lefutni ketszer.
- A hozzászóláshoz be kell jelentkezni
Ez egy érdekes téma, kérek róla infót, ha frissül.
--
blogom
- A hozzászóláshoz be kell jelentkezni
Ha a getBar() függvény nem const, akkor az értéke változhat a függvény ismételt lekérésével, ezért újra le kell kérnie.
Célszerűbb a 2. megoldást választani, ha hosszú futásidejű és nem const a függvény (a memóriát mindenképpen használod a visszatérési érték átmeneti tárolására az összehasonlítás erejéig, csak az első esetben nem nevesíted a memóriacímét).
Pl:
class foo{
private int i = 1;
int getBar(){
this.i += 2;
return this.i; }
}
Egyébként könnyen ellenőrizheted, hogy optimalizálás után hányszor hívódik meg, ha a függvénybe raksz egy printet...
- A hozzászóláshoz be kell jelentkezni
Ha a getBar() függvény nem const, akkor az értéke változhat a függvény ismételt lekérésével, ezért újra le kell kérnie.
C++ esetén nem hiszem hogy a const itt szerepet játszik, a consttal csak azt dekralálod, hogy az adott függvény nem változtatja meg az objektum állapotát. Még egymás utáni hívások során sem garantált hogy ugyanaz lesz a visszatérési érték, hiszen másik szál is változtathatja az objektum állapotát.
Amit a fordító csinálni tud, hogy inline-olja a függvény törzsét a hívás helyére, így megspórolva egy vtable lookup-ot (elég rég C++-oztam, úgyhogy lehet, hogy vannak más módszerek a vtable lookup kihagyására).
Ha a függvénybe print-et raksz, azzal pont kizárhatsz egy csomó optimalizációt. Egy print nagyon heavy művelet! Amit csinálni tudsz, hogy megnézed a generált kódot. Sok éve piszkáltam utoljára C++ fordítót, de már akkor is volt lehetőség megnézni a fordítási folyamat eredményét, nem kell feltétlenül a végső binárisban turkálni.
Java esetén ez még nehezebb, mert ott futásidőben dönthet úgy a HotSpot, hogy inline-ol egy függvényt (és más egyéb nyalánkságok).
Megj.: Ezt most mind csak fejből írom, semminek nem néztem utána, szóval tévedhetek is ;)
- A hozzászóláshoz be kell jelentkezni
Amikor Java-ban és C#-ban programozunk, akkor az ehhez hasonló mikro-mikro-mikrooptimalizálásnak semmiféle értelme nincsen. Nézd vissza javap -vel a generált kódot, és mérd ki hogy melyikkel jársz jobban ha az idősikot kinyújtod az egymilliószorosára.
Én a helyedben hagynám ezt a témát :)
szerk.: hogy egy kicsit épitő jellegű legyen a dolog, azt a változatot válaszd ami olvasható, tehát ha egy referenciát egynél többször szerzel be valahonnan akkor azt emeld ki változóba. Ez a best practice.
--
arch,xubuntu,debian,windows,android
dev: http://goo.gl/7Us0GN
BCI news: http://goo.gl/fvFM9C
- A hozzászóláshoz be kell jelentkezni