refined pointer and reference semantics
This commit is contained in:
parent
2e297f0769
commit
b04bc8736e
436
BASED.md
436
BASED.md
@ -132,13 +132,13 @@ A lényeg ez (kb. "kötelező" RAII + extra szabályok dangling ref / pointer el
|
|||||||
|
|
||||||
- referenciákat lehet paraméterként megadni elvárt típusnak ÉS visszatérési értékként is metódusoknál
|
- referenciákat lehet paraméterként megadni elvárt típusnak ÉS visszatérési értékként is metódusoknál
|
||||||
- De referenciát eltárolni handle mezőben nem lehet - sima változóban félreteheted magadnak a stack-en ha nem akarod mindig kiírni az expression-t.
|
- De referenciát eltárolni handle mezőben nem lehet - sima változóban félreteheted magadnak a stack-en ha nem akarod mindig kiírni az expression-t.
|
||||||
- Pointerekre meg van egy ownership modell, hogy a handle-khez (class/ struct megfelelője) tartozik egy "resources" vagy "pointers" blokk... Valszeg csak külön syntax van erre, nem akarok ilyen hosszú szavakat kiírva látni, mert öli a produktivitást, hanem mint a C++ban pl van az inicializálós rész a konstruktornál, úgy a class-nál valami hasonló kis syntax erre...
|
- Pointerekre meg van egy ownership modell, hogy a handle-khez tartoznak a pointereik. Csak ők írhatják őket (más nem) - típus szerint ők írhatják, tehát más azonos típusú handle-nek is írhatom!
|
||||||
- Ugye pointereket vissza tudnak adni és el tudnak fogadni paraméterként @resource-nak és @release-nek jelölt függvények (mint pl. a malloc meg a free).
|
- Ugye pointereket vissza tudnak adni és el tudnak fogadni paraméterként @resource-nak és @release-nek jelölt függvények (mint pl. a malloc meg a free). Ez egyszerűen kell az alap működéshez.
|
||||||
- Az ilyen (így jelölt) függvényeket, csak konstruktorból és destruktorból tudod meghívni!!!
|
- Az ilyen (így jelölt) függvényeket, csak konstruktorból és destruktorból tudod meghívni!!!
|
||||||
- De persze a konstruktor beállíthat a pointerednek valami értéket - nem kell malloc-ból inicializálja meg! és ugye nem kell free-szerű dolgokat hívnia rá...
|
- De persze a konstruktor beállíthat a pointerednek valami értéket - nem kell malloc-ból inicializálja meg! és ugye nem kell free-szerű dolgokat hívnia rá...
|
||||||
- Meg persze a @resource nem kötelező, hogy pointer-t adjon vissza - mert adhat valami handle-t is mondjuk. Tehát ennek a "pointeres" blokknak vagy lehetnek nem pointeres változói, ami mondjuk egy integer csak, de úgy jobb, ha minden resource-t szerintem oda írsz - alternatíva, hogy tényleg csak a pointereket jelöljük külön is és akkor sima mezőnek ad értéket a konstruktorban a @resource-os függvény ami mondjuk egy adatbázis handle-t ad vissza valami hívásból és tárol el.... Lehet hogy tisztább ez így, csak így a resource handle-k nem egy helyen vannak, hanem félig a pointerek - félig a sima adatok közt. Valszeg érdemes pointer blokkot csinálni ami tényleg csak pointereket tartalmaz és ha nem használod - hát nem használod.... Egyre inkább hajlok az utóbbira, mert mégis tisztábbnak hangzik...
|
- Meg persze a @resource nem kötelező, hogy pointer-t adjon vissza - mert adhat valami handle-t is mondjuk.
|
||||||
- Nyilván ezek mind privátok: de ha valaki referenciát ad paraméternek a saját típusodból egy metódusodban, az abban lévőket eléred mert van rá ugye visibility-d úgy. Meg esetleg ugye csinálhatsz friend jellegű dolgot, vagy "owner" class-t aki eléri a pointereid. Esetleg ha egymásba ágyazott class-okat csinálhatsz, akkor ez default bekerül friend-ként, hogy a class törzsében definiált másik class alapból a kintit tekinti ownernek... Ez az "owner class" jó ötletnek tűnik - el is neveztem protectornak - és így lehetnek protected pointerek - na azokat éri el az a speciális class / handler!
|
- Nyilván ezek mind privátok: de ha valaki referenciát ad paraméternek a saját típusodból egy metódusodban, az abban lévőket eléred mert van rá ugye visibility-d úgy. Meg esetleg ugye csinálhatsz friend jellegű dolgot, vagy "owner" class-t aki eléri a pointereid. Esetleg ha egymásba ágyazott class-okat csinálhatsz, akkor ez default bekerül friend-ként, hogy a class törzsében definiált másik class alapból a kintit tekinti ownernek... Ez az "owner class" jó ötletnek tűnik - el is neveztem protectornak - és így lehetnek protected pointerek - na azokat éri el az a speciális class / handler!
|
||||||
- A pointereknek adhatsz értéket, növelheted, csökkentheted, szorozhatod meg a faszomsetudja még mit akarsz vele. De csak a saját pointereidre van normális esetben láthatóságod...
|
- A pointereknek adhatsz értéket, növelheted, csökkentheted, szorozhatod meg a faszomsetudja még mit akarsz vele. De csak a saját pointereidre van normális esetben láthatóságod... Tehát más pointerei amit kapsz azokat nem tudod változtatni se értékadás jobb oldalában szerepeltetve "lementeni" magadnak.
|
||||||
- Alapból a pointer lehet null - alapból a referencia nem lehet null! Külön kell jelezni, ha lehet null egy visszaadott referencia, vagy paraméterben leírt ref - tehát ez a típus része. Mondjuk egy &ref? jellegű syntax, vagy valami ilyesmi.
|
- Alapból a pointer lehet null - alapból a referencia nem lehet null! Külön kell jelezni, ha lehet null egy visszaadott referencia, vagy paraméterben leírt ref - tehát ez a típus része. Mondjuk egy &ref? jellegű syntax, vagy valami ilyesmi.
|
||||||
|
|
||||||
^^ez szerintem majdnem mindent kezel, de nyilvánvalóan nem mindent és pont az a célom, hogy a rust-al ellentétben NE akarjak mindent safe-en tartani, csak minél többet...
|
^^ez szerintem majdnem mindent kezel, de nyilvánvalóan nem mindent és pont az a célom, hogy a rust-al ellentétben NE akarjak mindent safe-en tartani, csak minél többet...
|
||||||
@ -163,6 +163,231 @@ Ez meg szerintem fasság - nem jó érvek:
|
|||||||
|
|
||||||
https://dept-info.labri.fr/~strandh/Teaching/MTP/Common/Strandh-Tutorial/need-for-garbage-collection.html
|
https://dept-info.labri.fr/~strandh/Teaching/MTP/Common/Strandh-Tutorial/need-for-garbage-collection.html
|
||||||
|
|
||||||
|
## Jobb pointerek 1: safety
|
||||||
|
|
||||||
|
A pointerek ahogy fenn írtuk, alapvetően csak @resource és @release függvényekben menthetők és használhatók korlátlanul,
|
||||||
|
és persze a handle-k adattagjai is lehetnek pointerek amiket akkor kezelünk, mert saját pointer állhat értékadás bal oldalán!
|
||||||
|
|
||||||
|
VISZONT! Kiadhatunk pointereket a user kód / caller felé, de számukra nem "menthetők" le ezek. Tehát alapból egy pointer
|
||||||
|
nem lesz "menthető", de @resource-os és @release-es függvények esetén, @unsafe kódban és a saját ownership alatt IGEN!
|
||||||
|
|
||||||
|
Még egy fontos kivétel talán:
|
||||||
|
|
||||||
|
for (char* c = str; *c; ++c) {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
while (char* c = file.nextline()) {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
^^Ennél szerintem engedni kéne, hogy a for/while használhassa talán? De ez egy nagyon jó kérdés, mert speciális eset!
|
||||||
|
Szerintem valahogy ez jó volna, mert praktikus baj nincs vele és C-interop miatt kellhet (főleg az utóbbi pl.)
|
||||||
|
|
||||||
|
## Jobb pointerek 2: speed
|
||||||
|
|
||||||
|
Valahogy jó lenne elérni, hogy a default a "restrict" kulcsszóhoz legyen közelebb.
|
||||||
|
Egy lehetőség, hogy tömbre (tehát ptr+méret-re)a default a restrict, de pointerre ki kell írni.
|
||||||
|
|
||||||
|
* Talán a tömbök külön típussá emelése ebben már eleve segít - igen... legyenek továbbá mérettel is ellátva stb. lásd ott
|
||||||
|
* Mivel az ownerek kezelnek csak ptr-eket, ők adhatnk ki referenciákat és array-eket, de azok ugye nem alias-olnak be könnyen
|
||||||
|
* A restrict kulcsszó támogatása a minimum - de talán kéne gondolkozni mit lehetne még kihozni, hogy jobb legyen!
|
||||||
|
* Ugye mivel van külön "tömb" típusunk, ezért arra is lehetővé kéne tenni a restrict-et pl. paramétereknél, különben bakis lesz.
|
||||||
|
|
||||||
|
Egy (talán elvetendő) ötlet: A pointer típusa lehessen csoportosítható:
|
||||||
|
|
||||||
|
handle A {
|
||||||
|
int n;
|
||||||
|
};
|
||||||
|
|
||||||
|
A@aliasinggroup1 variable = { 1 };
|
||||||
|
A@aligrp2 array[5] = { 1, 2, 3, 4, 5 };
|
||||||
|
|
||||||
|
inline int magic(A *ptr1, A *ptr2) {
|
||||||
|
ptr2->n = 42;
|
||||||
|
return ptr1->n; // return 1 when inlined
|
||||||
|
}
|
||||||
|
|
||||||
|
magic(&variable, &array[3]); // see that it cannot alias
|
||||||
|
|
||||||
|
Esetleg szükséges legyen ilyenkor a használati helyen is kiírni a típust?
|
||||||
|
|
||||||
|
inline int magic(A@aliasinggroup1 *ptr1, A@aligrp2 *ptr2);
|
||||||
|
|
||||||
|
Az a bajom ezzel... hogy "gyalázatosan ocsmány" sajnos ez mind...
|
||||||
|
|
||||||
|
Alternatíva:
|
||||||
|
|
||||||
|
* Tömbökről feltételezzük, hogy sosem alias-olnak be (hacsak nem "unrestrict" kulcsszavas).
|
||||||
|
* Referenciák sem alias-olódnak
|
||||||
|
|
||||||
|
Alternatíva (all-in):
|
||||||
|
|
||||||
|
* Mindent restrict-nek veszünk by default és az unrestrict-et be kell írni...
|
||||||
|
* Kicsit unsafe-nek hangzik és sok benne a meglepetés, de a perf jó.
|
||||||
|
|
||||||
|
Lásd FORTRAN: https://flang.llvm.org/docs/Aliasing.html
|
||||||
|
|
||||||
|
Alternatíva (typedef-szerű):
|
||||||
|
|
||||||
|
restrictptr PooledPtr int*;
|
||||||
|
restrictptr RawPtr int*;
|
||||||
|
|
||||||
|
inline int magic(PooledPtr ptr1, RawPtr ptr2) {
|
||||||
|
*ptr2 = 42;
|
||||||
|
return *ptr1; // Should optimize as: return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
int i1 = 1;
|
||||||
|
int i2 = 2;
|
||||||
|
magic(&i1, &i2); // can optimize
|
||||||
|
|
||||||
|
Jelenleg ez az alternatíva tetszik a legjobban + persze a restrict kulcsszó támogatása emellett még pluszban!
|
||||||
|
+ Talán lehetne restrictptr PooledArr int[]; jellegű típus definíció is (tömbök alias kerülésére)
|
||||||
|
|
||||||
|
Szemantika:
|
||||||
|
|
||||||
|
* Ha csak különböző ilyeneket látok egy paraméterlistán, akkor restrict-et kódgenerálunk mindre.
|
||||||
|
* Ha csak különböző ilyeneket ÉS más típusú dolgokat... akkor is...
|
||||||
|
* Egy ugyan ilyen típusú és egy másik ugyan ilyen típusú restricptr között viszont kell aliasing!
|
||||||
|
* Egy (fenti példával élve) PooledPtr-es int* és egy sima int* között viszont nem kell!
|
||||||
|
* Van továbbra is restrict kulcsszó (C-s szemantikával)
|
||||||
|
|
||||||
|
Ezekkel a szabályokkal egész jó dolgot csináltunk szerintem, mert ha valaki kiad egy pointert, akkor kiadhat
|
||||||
|
hozzá ilyen plusz infókat, ami jelzi a fordítónak, hogy mik alias-olhatnak és mik nem. Egy jó példa, ha egy
|
||||||
|
játék mondjuk ilyen memory arénákban tárol dolgokat és az "engine" tudhatja, hogy melyik arénából adjuk ki
|
||||||
|
éppen a mutatót. Gyakorlatilag ez majdnem olyan, mintha valamit restrictptr-el deklarálunk, akkor annak
|
||||||
|
az összes előfordulásánál beíródik a generált pointer elé a "restrict" - ez csak annyiban téves, hogy
|
||||||
|
ha több ilyen is van egy függvényben / scope-ban azonos típusnéven, akkor azok egymással nem restrict-elnek!
|
||||||
|
Ez utóbbit első körben implementálhatjuk úgy, hogy ilyenkor lekerül a restrict, de úgy is, hogy kódgenerálással
|
||||||
|
megoldjuk csak a kettő közötti restrict-álást és fenn hagyjuk a kulcsszót. Ez egy kicsit bonyolítja a dolgokat...
|
||||||
|
|
||||||
|
Az ilyen pointer cast-olható az alaptípusára - de ezzel elveszítjük a speed boost lehetőségét olyankor!
|
||||||
|
Megj.: Szerintem ezzel generálható C-kód -fno-strict-aliasing mellett is akár, ha típus alapút nem akarok!
|
||||||
|
|
||||||
|
## Jobb referenciák
|
||||||
|
|
||||||
|
* A referenciának lehetne "értéket adni" - nem mint C++ban...
|
||||||
|
* De ahogy fentebb írtuk másolni csak lokális változóba lehet a stackre
|
||||||
|
* A referencia "lényegében egy pointer, de nincs rajta aritmetika"... szóval nem tömböt jelképez.
|
||||||
|
* Kérdőjel hozzáadásával jelölhető, hogy null lehet-e (default: nem).
|
||||||
|
|
||||||
|
## Jobb tömbök
|
||||||
|
|
||||||
|
Néhány dologra van szerintem szükségünk:
|
||||||
|
|
||||||
|
* (Pointer + méret / range) jellegű tömb történet, ahol a méret / range lekérhető a típusból ADA-szerűen (.len) és az elemtípus is!
|
||||||
|
* Range subtype (ada szerűen) NEM kell külön a típusrendszerben, mert hajlok arra, hogy 0..méret lehessen csak (praktikus: dyn)
|
||||||
|
* Dinamikus esetben, hívhasd az ilyen függvény paramétert sort(array(ptr, 42)) történettel (ahol ptr egy pointer, 42 méret).
|
||||||
|
|
||||||
|
A fordító két függvényt is generál ilyen függvényekből. A típusrendszer szerint azt a függvényt hívjuk meg, amelyik gyorsabb!
|
||||||
|
Igazából ha fordítási időben ismert méretű tömbbel hívjuk meg, akkor a "sima" függvény hívódik, kivéve, ha ez valami linkelt lib,
|
||||||
|
mert abban az esetben természetesen a "valódi" függvény fog hívódni - ezt C-re fordításnál még nem látom át hogyan kéne, de majd
|
||||||
|
meg kellene oldani...
|
||||||
|
|
||||||
|
A lényeg, hogy a "tömb" típus az pointer + hossz minden esetben, de ahol tudja, a fordító kioptimalizálja a történetet!
|
||||||
|
Ha valami mást akar az ember, például performancia okokból, akkor simán adjon át egy pointert (és mondjuk null termináns).
|
||||||
|
|
||||||
|
Ha az "array" második paramétere nem fordítási idejű konstans szám, akkor ott is a dinamikus függvényt hívjuk majd meg.
|
||||||
|
|
||||||
|
A range, mint altípus megadás lehetővé tenné, hogy dolgok "osztozzanak" a méreten - pl. ECS egy játékmotorban, stb.
|
||||||
|
De mivel nem tól-ig, vagyis ADA-szerű range-jeink vannak, simán csak méret, ezért ha ilyet akarsz konstans számként tárolod.
|
||||||
|
|
||||||
|
Emellett természetesen a standard library-ban kell "vector" típus és az tudjon array-t is adni neked.
|
||||||
|
|
||||||
|
Szerintem az így kapott kód "már kellően biztonságos" általában. A vektorra debug build esetén legyen range check kötelezően!
|
||||||
|
|
||||||
|
A tömböknél a restrict lehet ESETLEG a default - de ennek fényében kellene unrestrict kulcsszó rájuk!
|
||||||
|
|
||||||
|
Lásd még:
|
||||||
|
|
||||||
|
https://youtu.be/MUISz2qA640?t=1980
|
||||||
|
|
||||||
|
ui.: Ha nem lesz operátor overload, akkor szerintem legyen több dimenziós tömb (nem a tömbök tömbje, hanem: matrix[15, 10, 42] = 5;
|
||||||
|
|
||||||
|
### restrict-ált tömbök
|
||||||
|
|
||||||
|
A tömbökre is kell a pointerekhez hasonló restrict.
|
||||||
|
|
||||||
|
## Típusos indexek
|
||||||
|
|
||||||
|
Egy pointer a modern gépen 64 bit, sokszor tömörebben tudsz tárolni "referencia-szerűséget" ha indexet tárolsz rá!
|
||||||
|
|
||||||
|
struct A { uint32_t a, b, c; }; // 3*4=12 bytes with 4 byte alignment
|
||||||
|
struct B { child *a, *b, *c; }; // 3*8=24 bytes with 8 byte alignment
|
||||||
|
|
||||||
|
struct C { A aa; uint32_t x; }; // 4*4=16 bytes with 4 byte alignment
|
||||||
|
struct D { B bb; uint32_t x; }; // 4*8=32 bytes with 8 byte alignment (4 wasted bytes)
|
||||||
|
|
||||||
|
Viszont! A pointerek - mint itt is látszik - típusosak, de az indexek NEM! Viszont miért ne lehetnének?
|
||||||
|
|
||||||
|
child@uint32_t index; // Például így
|
||||||
|
|
||||||
|
A syntax-on lehetne még variálni...
|
||||||
|
|
||||||
|
De az a helyzet, hogy erre már van megoldás jelenleg is (sőt C-ben is, struct-al):
|
||||||
|
|
||||||
|
record ChildIndex {
|
||||||
|
uint32_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
esetleg még jobb, ha "like-olást" is használunk közben:
|
||||||
|
|
||||||
|
record child_index {
|
||||||
|
like uint32_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
### NonOwningPointer - smartpointer
|
||||||
|
|
||||||
|
Ezt szerintem érdemes C++ template-el előre megcsinálni! A lényeg az, hogy kreálni lehet a dolgot - tehát van egy factory-szerű,
|
||||||
|
|
||||||
|
class Owner {
|
||||||
|
private:
|
||||||
|
std::vector<int> vec = {1, 2, 3};
|
||||||
|
|
||||||
|
// Has a vector of pointers to children
|
||||||
|
// So when this go out of scope we can set child pointers backpointers to nulls (indicating non-living)
|
||||||
|
OwnPtrFactory<int> fac;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// or access(..) etc.
|
||||||
|
// NownPtr has a pointer to the factory's own elem and on its move assign / move constructor calls updates facs child vec's
|
||||||
|
// pointer. Caller can ask the pointer if owner is already deleted or not. Maybe should be threadsafe?
|
||||||
|
NownPtr<int> getMid() {
|
||||||
|
return fac.create(&vec[1]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ezt követően a használati ponton olyasmi történik, hogy:
|
||||||
|
|
||||||
|
class User {
|
||||||
|
void f(..) {
|
||||||
|
...
|
||||||
|
// Safe használat
|
||||||
|
NownPtr mid = owner.getMid();
|
||||||
|
mid.if_exists([](int &i) {
|
||||||
|
... i is accessible ...
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unsafe használat (esetleg nálam @unsafe és konstruktor/destruktor esetben?)
|
||||||
|
int j = *mid + 40;
|
||||||
|
|
||||||
|
// Unsage-nél is lehessen kérdezni
|
||||||
|
if(mid) {
|
||||||
|
int k = *mid;
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ez tehát úgy látom C++ nyelven is lehetséges, egyedül mozgatáskor tűnik kicsit is "lassabbnak" meg ugye ez egy "fat" pointer, ami
|
||||||
|
2x pointernyi méretet használ fel... az egyik maga a mutatott terület címe, a másik a "factory"-ban lévő vektor megfelelő elemére
|
||||||
|
mutat rá - itt vigyázni kell: a vektor resize esetén invalidálódik ez! Kéne valami "stabil-vektor" ami blokkos és a resize-nál NEM
|
||||||
|
invalidálódik, de nem is egyesével, hanem blokkosával láncolt lista!
|
||||||
|
|
||||||
|
Ezt esetleg nem is nyelvi elemként tennénk be, hanem library-ként a BASED-be is, az talán jobb?
|
||||||
|
|
||||||
## Custom allokátorok / arénák
|
## Custom allokátorok / arénák
|
||||||
|
|
||||||
A go-hoz újabban ajánlott "arénás" modell-t is lehet hogy valahogy alkalmazhatnánk - tehát hogy azzal oldjuk meg a custom allokátor kérdést... De szerintem egyszerűen úgy kell kialakítani a standard libet / kódokat, hogy template paraméter szerűen átvegye milyen @resource és @release függvényeket használ - tehát nem konkrétan malloc-ot, meg free-t mondjuk hanem csak azoknak megfelelő deklarációs függvényeket... Ezt C-ben is szoktam csinálni (pl. a kismap-ban van ilyen), de ugye ott függvény pointerekkel, amiket vagy eltárolok (de akkor runtime költsége van), vagy ha a perf fontos, akkor pl. a kismap esetén azt szoktam, hogy ezeket nem csak konstruálásnál, hanem minden hívásnál át kell adni ami használja - azért mert látom, hogy a compiler be tudja inline-olni, ha egy static inline force-inline stb. függvényt adok át neki function pointernek... De ez ugye "trükközgetés" feleslegesen és jó lenne, ha nyelvi elemként tudnánk ezt. Esetleg érdemes a hagyományos generik / template témától ezt különszedni? Nem biztos, lehet hogy azzal kezelendő. Cpp esetén template-ekkel szoktam ezt csinálni...
|
A go-hoz újabban ajánlott "arénás" modell-t is lehet hogy valahogy alkalmazhatnánk - tehát hogy azzal oldjuk meg a custom allokátor kérdést... De szerintem egyszerűen úgy kell kialakítani a standard libet / kódokat, hogy template paraméter szerűen átvegye milyen @resource és @release függvényeket használ - tehát nem konkrétan malloc-ot, meg free-t mondjuk hanem csak azoknak megfelelő deklarációs függvényeket... Ezt C-ben is szoktam csinálni (pl. a kismap-ban van ilyen), de ugye ott függvény pointerekkel, amiket vagy eltárolok (de akkor runtime költsége van), vagy ha a perf fontos, akkor pl. a kismap esetén azt szoktam, hogy ezeket nem csak konstruálásnál, hanem minden hívásnál át kell adni ami használja - azért mert látom, hogy a compiler be tudja inline-olni, ha egy static inline force-inline stb. függvényt adok át neki function pointernek... De ez ugye "trükközgetés" feleslegesen és jó lenne, ha nyelvi elemként tudnánk ezt. Esetleg érdemes a hagyományos generik / template témától ezt különszedni? Nem biztos, lehet hogy azzal kezelendő. Cpp esetén template-ekkel szoktam ezt csinálni...
|
||||||
@ -294,209 +519,6 @@ Lásd:
|
|||||||
|
|
||||||
Ez szerintem low-level helyekre jó lehet... Ha jól definiálom mi történik, akkor padding / align helyett (mellett) is talán.
|
Ez szerintem low-level helyekre jó lehet... Ha jól definiálom mi történik, akkor padding / align helyett (mellett) is talán.
|
||||||
|
|
||||||
## Jobb pointerek
|
|
||||||
|
|
||||||
Valahogy jó lenne elérni, hogy a default a "restrict" kulcsszóhoz legyen közelebb.
|
|
||||||
Egy lehetőség, hogy tömbre (tehát ptr+méret-re)a default a restrict, de pointerre ki kell írni.
|
|
||||||
|
|
||||||
* Talán a tömbök külön típussá emelése ebben már eleve segít - igen... legyenek továbbá mérettel is ellátva stb. lásd ott
|
|
||||||
* Mivel az ownerek kezelnek csak ptr-eket, ők adhatnk ki referenciákat és array-eket, de azok ugye nem alias-olnak be könnyen
|
|
||||||
* A restrict kulcsszó támogatása a minimum - de talán kéne gondolkozni mit lehetne még kihozni, hogy jobb legyen!
|
|
||||||
* Ugye mivel van külön "tömb" típusunk, ezért arra is lehetővé kéne tenni a restrict-et pl. paramétereknél, különben bakis lesz.
|
|
||||||
|
|
||||||
Egy (talán elvetendő) ötlet: A pointer típusa lehessen csoportosítható:
|
|
||||||
|
|
||||||
handle A {
|
|
||||||
int n;
|
|
||||||
};
|
|
||||||
|
|
||||||
A@aliasinggroup1 variable = { 1 };
|
|
||||||
A@aligrp2 array[5] = { 1, 2, 3, 4, 5 };
|
|
||||||
|
|
||||||
inline int magic(A *ptr1, A *ptr2) {
|
|
||||||
ptr2->n = 42;
|
|
||||||
return ptr1->n; // return 1 when inlined
|
|
||||||
}
|
|
||||||
|
|
||||||
magic(&variable, &array[3]); // see that it cannot alias
|
|
||||||
|
|
||||||
Esetleg szükséges legyen ilyenkor a használati helyen is kiírni a típust?
|
|
||||||
|
|
||||||
inline int magic(A@aliasinggroup1 *ptr1, A@aligrp2 *ptr2);
|
|
||||||
|
|
||||||
Az a bajom ezzel... hogy "gyalázatosan ocsmány" sajnos ez mind...
|
|
||||||
|
|
||||||
Alternatíva:
|
|
||||||
|
|
||||||
* Tömbökről feltételezzük, hogy sosem alias-olnak be (hacsak nem "unrestrict" kulcsszavas).
|
|
||||||
* Referenciák sem alias-olódnak
|
|
||||||
|
|
||||||
Alternatíva (all-in):
|
|
||||||
|
|
||||||
* Mindent restrict-nek veszünk by default és az unrestrict-et be kell írni...
|
|
||||||
* Kicsit unsafe-nek hangzik és sok benne a meglepetés, de a perf jó.
|
|
||||||
|
|
||||||
Lásd FORTRAN: https://flang.llvm.org/docs/Aliasing.html
|
|
||||||
|
|
||||||
Alternatíva (typedef-szerű):
|
|
||||||
|
|
||||||
restrictptr PooledPtr int*;
|
|
||||||
restrictptr RawPtr int*;
|
|
||||||
|
|
||||||
inline int magic(PooledPtr ptr1, RawPtr ptr2) {
|
|
||||||
*ptr2 = 42;
|
|
||||||
return *ptr1; // Should optimize as: return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
int i1 = 1;
|
|
||||||
int i2 = 2;
|
|
||||||
magic(&i1, &i2); // can optimize
|
|
||||||
|
|
||||||
Jelenleg ez az alternatíva tetszik a legjobban + persze a restrict kulcsszó támogatása emellett még pluszban!
|
|
||||||
+ Talán lehetne restrictptr PooledArr int[]; jellegű típus definíció is (tömbök alias kerülésére)
|
|
||||||
|
|
||||||
Szemantika:
|
|
||||||
|
|
||||||
* Ha csak különböző ilyeneket látok egy paraméterlistán, akkor restrict-et kódgenerálunk mindre.
|
|
||||||
* Ha csak különböző ilyeneket ÉS más típusú dolgokat... akkor is...
|
|
||||||
* Egy ugyan ilyen típusú és egy másik ugyan ilyen típusú restricptr között viszont kell aliasing!
|
|
||||||
* Egy (fenti példával élve) PooledPtr-es int* és egy sima int* között viszont nem kell!
|
|
||||||
* Van továbbra is restrict kulcsszó (C-s szemantikával)
|
|
||||||
|
|
||||||
Ezekkel a szabályokkal egész jó dolgot csináltunk szerintem, mert ha valaki kiad egy pointert, akkor kiadhat
|
|
||||||
hozzá ilyen plusz infókat, ami jelzi a fordítónak, hogy mik alias-olhatnak és mik nem. Egy jó példa, ha egy
|
|
||||||
játék mondjuk ilyen memory arénákban tárol dolgokat és az "engine" tudhatja, hogy melyik arénából adjuk ki
|
|
||||||
éppen a mutatót. Gyakorlatilag ez majdnem olyan, mintha valamit restrictptr-el deklarálunk, akkor annak
|
|
||||||
az összes előfordulásánál beíródik a generált pointer elé a "restrict" - ez csak annyiban téves, hogy
|
|
||||||
ha több ilyen is van egy függvényben / scope-ban azonos típusnéven, akkor azok egymással nem restrict-elnek!
|
|
||||||
Ez utóbbit első körben implementálhatjuk úgy, hogy ilyenkor lekerül a restrict, de úgy is, hogy kódgenerálással
|
|
||||||
megoldjuk csak a kettő közötti restrict-álást és fenn hagyjuk a kulcsszót. Ez egy kicsit bonyolítja a dolgokat...
|
|
||||||
|
|
||||||
Az ilyen pointer cast-olható az alaptípusára - de ezzel elveszítjük a speed boost lehetőségét olyankor!
|
|
||||||
Megj.: Szerintem ezzel generálható C-kód -fno-strict-aliasing mellett is akár, ha típus alapút nem akarok!
|
|
||||||
|
|
||||||
## Jobb referenciák
|
|
||||||
|
|
||||||
* A referenciának lehetne "értéket adni" - nem mint C++ban...
|
|
||||||
* A referencia "lényegében egy pointer, de nincs rajta aritmetika"...
|
|
||||||
* Kérdőjel hozzáadásával jelölhető, hogy null lehet-e (default: nem).
|
|
||||||
|
|
||||||
## Jobb tömbök
|
|
||||||
|
|
||||||
Néhány dologra van szerintem szükségünk:
|
|
||||||
|
|
||||||
* (Pointer + méret / range) jellegű tömb történet, ahol a méret / range lekérhető a típusból ADA-szerűen (.len) és az elemtípus is!
|
|
||||||
* Range subtype (ada szerűen) NEM kell külön a típusrendszerben, mert hajlok arra, hogy 0..méret lehessen csak (praktikus: dyn)
|
|
||||||
* Dinamikus esetben, hívhasd az ilyen függvény paramétert sort(array(ptr, 42)) történettel (ahol ptr egy pointer, 42 méret).
|
|
||||||
|
|
||||||
A fordító két függvényt is generál ilyen függvényekből. A típusrendszer szerint azt a függvényt hívjuk meg, amelyik gyorsabb!
|
|
||||||
Igazából ha fordítási időben ismert méretű tömbbel hívjuk meg, akkor a "sima" függvény hívódik, kivéve, ha ez valami linkelt lib,
|
|
||||||
mert abban az esetben természetesen a "valódi" függvény fog hívódni - ezt C-re fordításnál még nem látom át hogyan kéne, de majd
|
|
||||||
meg kellene oldani...
|
|
||||||
|
|
||||||
A lényeg, hogy a "tömb" típus az pointer + hossz minden esetben, de ahol tudja, a fordító kioptimalizálja a történetet!
|
|
||||||
Ha valami mást akar az ember, például performancia okokból, akkor simán adjon át egy pointert (és mondjuk null termináns).
|
|
||||||
|
|
||||||
Ha az "array" második paramétere nem fordítási idejű konstans szám, akkor ott is a dinamikus függvényt hívjuk majd meg.
|
|
||||||
|
|
||||||
A range, mint altípus megadás lehetővé tenné, hogy dolgok "osztozzanak" a méreten - pl. ECS egy játékmotorban, stb.
|
|
||||||
De mivel nem tól-ig, vagyis ADA-szerű range-jeink vannak, simán csak méret, ezért ha ilyet akarsz konstans számként tárolod.
|
|
||||||
|
|
||||||
Emellett természetesen a standard library-ban kell "vector" típus és az tudjon array-t is adni neked.
|
|
||||||
|
|
||||||
Szerintem az így kapott kód "már kellően biztonságos" általában. A vektorra debug build esetén legyen range check kötelezően!
|
|
||||||
|
|
||||||
A tömböknél a restrict lehet ESETLEG a default - de ennek fényében kellene unrestrict kulcsszó rájuk!
|
|
||||||
|
|
||||||
Lásd még:
|
|
||||||
|
|
||||||
https://youtu.be/MUISz2qA640?t=1980
|
|
||||||
|
|
||||||
ui.: Ha nem lesz operátor overload, akkor szerintem legyen több dimenziós tömb (nem a tömbök tömbje, hanem: matrix[15, 10, 42] = 5;
|
|
||||||
|
|
||||||
### restrict-ált tömbök
|
|
||||||
|
|
||||||
A tömbökre is kell a pointerekhez hasonló restrict.
|
|
||||||
|
|
||||||
## Típusos indexek
|
|
||||||
|
|
||||||
Egy pointer a modern gépen 64 bit, sokszor tömörebben tudsz tárolni "referencia-szerűséget" ha indexet tárolsz rá!
|
|
||||||
|
|
||||||
struct A { uint32_t a, b, c; }; // 3*4=12 bytes with 4 byte alignment
|
|
||||||
struct B { child *a, *b, *c; }; // 3*8=24 bytes with 8 byte alignment
|
|
||||||
|
|
||||||
struct C { A aa; uint32_t x; }; // 4*4=16 bytes with 4 byte alignment
|
|
||||||
struct D { B bb; uint32_t x; }; // 4*8=32 bytes with 8 byte alignment (4 wasted bytes)
|
|
||||||
|
|
||||||
Viszont! A pointerek - mint itt is látszik - típusosak, de az indexek NEM! Viszont miért ne lehetnének?
|
|
||||||
|
|
||||||
child@uint32_t index; // Például így
|
|
||||||
|
|
||||||
A syntax-on lehetne még variálni...
|
|
||||||
|
|
||||||
De az a helyzet, hogy erre már van megoldás jelenleg is (sőt C-ben is, struct-al):
|
|
||||||
|
|
||||||
record ChildIndex {
|
|
||||||
uint32_t index;
|
|
||||||
};
|
|
||||||
|
|
||||||
esetleg még jobb, ha "like-olást" is használunk közben:
|
|
||||||
|
|
||||||
record child_index {
|
|
||||||
like uint32_t index;
|
|
||||||
};
|
|
||||||
|
|
||||||
### NonOwningPointer - smartpointer
|
|
||||||
|
|
||||||
Ezt szerintem érdemes C++ template-el előre megcsinálni! A lényeg az, hogy kreálni lehet a dolgot - tehát van egy factory-szerű,
|
|
||||||
|
|
||||||
class Owner {
|
|
||||||
private:
|
|
||||||
std::vector<int> vec = {1, 2, 3};
|
|
||||||
|
|
||||||
// Has a vector of pointers to children
|
|
||||||
// So when this go out of scope we can set child pointers backpointers to nulls (indicating non-living)
|
|
||||||
OwnPtrFactory<int> fac;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// or access(..) etc.
|
|
||||||
// NownPtr has a pointer to the factory's own elem and on its move assign / move constructor calls updates facs child vec's
|
|
||||||
// pointer. Caller can ask the pointer if owner is already deleted or not. Maybe should be threadsafe?
|
|
||||||
NownPtr<int> getMid() {
|
|
||||||
return fac.create(&vec[1]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ezt követően a használati ponton olyasmi történik, hogy:
|
|
||||||
|
|
||||||
class User {
|
|
||||||
void f(..) {
|
|
||||||
...
|
|
||||||
// Safe használat
|
|
||||||
NownPtr mid = owner.getMid();
|
|
||||||
mid.if_exists([](int &i) {
|
|
||||||
... i is accessible ...
|
|
||||||
});
|
|
||||||
|
|
||||||
// Unsafe használat (esetleg nálam @unsafe és konstruktor/destruktor esetben?)
|
|
||||||
int j = *mid + 40;
|
|
||||||
|
|
||||||
// Unsage-nél is lehessen kérdezni
|
|
||||||
if(mid) {
|
|
||||||
int k = *mid;
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ez tehát úgy látom C++ nyelven is lehetséges, egyedül mozgatáskor tűnik kicsit is "lassabbnak" meg ugye ez egy "fat" pointer, ami
|
|
||||||
2x pointernyi méretet használ fel... az egyik maga a mutatott terület címe, a másik a "factory"-ban lévő vektor megfelelő elemére
|
|
||||||
mutat rá - itt vigyázni kell: a vektor resize esetén invalidálódik ez! Kéne valami "stabil-vektor" ami blokkos és a resize-nál NEM
|
|
||||||
invalidálódik, de nem is egyesével, hanem blokkosával láncolt lista!
|
|
||||||
|
|
||||||
Ezt esetleg nem is nyelvi elemként tennénk be, hanem library-ként a BASED-be is, az talán jobb?
|
|
||||||
|
|
||||||
## Holy-C féle kiegészítések a switch statement-hez
|
## Holy-C féle kiegészítések a switch statement-hez
|
||||||
|
|
||||||
Erről még nem vagyok meggyőzve, de néha hasznosnak tűnnek:
|
Erről még nem vagyok meggyőzve, de néha hasznosnak tűnnek:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user