Az előző bejegyzésben kiderült, hogy már majdnem jó az, ha a komponenseket a system, vagy a system manager hozza létre, és tárolja, de a memory fragmentation problémája (továbbra is) fennáll. Emellett érdemes megemlíteni a cache-friendly kód fogalmát.
A memória elérése alapvetően lassú, így hiába írnánk gyors algoritmust, ha a processzoridő nagy része idle, mert az adatra várunk, hogy megérkezzen. Tudjuk, hogy a processzornak vannak regiszterei, de ezek alapvetően drágák, és igen kicsik, cserébe piszok gyorsak. Egy jó ideje már van ún. cache memory, ami árban és sebességben is a register és a main memory között van. A cache-ből nagyságrendekkel gyorsabb a kiolvasás, mint a fő memóriából, így egy cache-friendly megközelítés sokat gyorsíthat a kódon.
Tehát az össze-vissza dinamikus memóriafoglalás nem csak a "töredezettség" miatt problémás, hanem a cache szempontjából sem túl előnyös. Általában egy memóriaolvasáskor nem csak az adott méretű területet olvassa ki, hanem annál többet, így közvetlen a (cím+méret) utáni memóriaelérés "ingyen" van. Tehát, ha az adataink a memóriában egymást követik, az azokon való "végigiterálás" gyorsabb, mintha össze-vissza ugrálnánk a memóriában. Azaz az sem mindegy, hogy milyen adatszerkezetben tároljuk az adatainkat.
Tipikus példaként az std::vector vs std::list párost szokták felhozni.
A vector tulajdonképpen egy tömb, amelynek dinamikusan változhat a mérete. Azonban, ha növekszik a szükséges méret (például eddig maximum 4 elemet tudott tárolni, de hozzá szeretnénk adni egy újabbat), akkor újra kell foglalni a tömböt, és átmásolni az adatokat az új tömbbe. Hasonlóan, ha egy elemet eltávolítunk a vector közepéről, akkor az azt követő elemeket át kell helyezni/másolni eggyel "előrébb", ezzel biztosítva a folytonosságot a memóriában.
Ezzel szemben a listába való beszúrás és törlés bármely pozícióban konstans idejű, hiszen csak néhány pointert kell beállítani. Cserébe a listának az elemei a memóriában "random helyeken" lesznek, így a listán való végigiterálás kevésbé hatékony (sok cache miss).
Ezen szempontok mellett egy harmadik igen fontos különbség is van a kettő között, mégpedig a pointer/reference/iterator stabilitás.
Ha egy listába beszúrunk egy elemet, akkor annak a címe érvényes marad egészen addig, amíg az adott elemet el nem távolítjuk a listából, függetlenül attól, hogy másik elemet hozzáadtunk vagy eltávolítottunk. Ez pedig nyilván abból adódik, hogy a lista elemei egymástól függetlenek.
Azonban a vector ezt nem tudja, hiszen a belső tömb újrafoglalásakor is és egy adott elem eltávolításakor (azaz a többi mozgatásakor) is változik az eddigi elemeknek a címe. A következő egyszerű mintakódban a "belső tömbre" többször hivatkozok, ezalatt a vectornak a saját belső adatszerkezetére gondolok (ami tulajdonképpen egy tömb):
std::vector<int> vec; // lekérjük, hogy a belső tömb maximum hány elemet tud tárolni size_t currMax = vec.capacity(); // átméretezzük a vectort, hogy maximálisan kitöltse a belső tömböt vec.resize(currMax); // eltároljuk az első elemre mutató pointert int* p = &vec[0]; // ezek után, ha beszúrunk egy elemet, akkor biztos, // hogy újra kell allokálnia a belső tömböt vec.push_back(0); // a vector első elemének a címe nem egyezik meg az eltárolt címmel: &vec[0] != p // emellett az előző belső tömb már fel lett szabadítva // tehát, ha megpróbáljuk dereferálni a pointert, simán crashelhet a program std::cout << *p;
Ezeket az információkat egyébként megtudhatjuk a megfelelő oldalt böngészve. Ha megnézzük például a vectornak a push_back és erase függvényeit, és kicsit lentebb tekerünk, akkor láthatjuk a Complexity, Iterator validity és Data races részeket, amelyek általában igen pontosan ismertetik ezeket a tényezőket. A push_backnél például azt írják, hogy realloc esetén (amely például a belső tömb növekedésekor történik) az iterátorok és pointerek invaliddá válnak.
If a reallocation happens, all iterators, pointers and references related to the container are invalidated.
Tehát vagy feladjuk a pointer stabilitást (azaz a GameObject osztályunk nem tud simán pointereket tárolni a komponensekre), vagy a memória-folytonosságot.
Vagy trükközünk egy picit. Folytatás később... :)