|
|
|
![]() |
|
Strumenti |
![]() |
#1 |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
[C++] Override di new e custom allocators
Salve,
prima di tutto volevo chiedere consiglio su Nedmalloc, un malloc custom scritto da non so chi... lo ha consigliato il founder di Ogre3D e in effetti è capace di una pazza velocità pari a 125x il malloc di Windows, provato con un test sulla mia macchina (20.000.000 di malloc free di doubles) ![]() In sostanza, perchè non lo usano tutti? E dove sta la fregatura? ![]() Cmq, dopo aver scoperto questo coso, mi sono posto il problema di rimpiazzare tutti i new, alloc, malloc, delete eccetera del mio framework con i ned-corrispettivi... solo che mi sono scontrato con tutti quanti i limiti del C++! Dopo parecchia fatica sono riuscito a creare una classe e uno schifo statico che mi permettono di scegliere a runtime l'interfaccia della memoria, ma non sono ancora soddisfatto, anche se funziona: Ecco il codice: Interfaccia: Codice:
#ifndef MEMORY_ALLOCATOR_INTERFACE_H #define MEMORY_ALLOCATOR_INTERFACE_H namespace eVolve { namespace Core { ///Interface class to be used to have a custom memory allocator /** See also the GameServer constructor. */ class EVOLVEEXPORT MemoryAllocatorInterface { public: ///pure allocator virtual void* malloc(unsigned int size)=0; virtual void* realloc(void* memory, unsigned int size)=0; ///new call (allocates and then the runtime calls constructor virtual void* instance(unsigned int size)=0; ///delete + free call virtual void free(void* memory)=0; }; } } #endif Codice:
#ifndef MEMORY_ALLOCATOR_WRAPPER_H #define MEMORY_ALLOCATOR_WRAPPER_H #include "eVolve_Conf.h" #ifndef PREVENT_CUSTOM_MEMORY_ALLOCATORS #include "MemoryAllocatorInterface.h" //give some pretty names xD #define EVOLVE_MALLOC eVolve_malloc #define EVOLVE_REALLOC eVolve_realloc #define EVOLVE_NEW new #define EVOLVE_DELETE delete namespace eVolve { namespace Core { class MemoryAllocatorInterface; class EVOLVEEXPORT MemoryAllocatorWrapper { public: //Be careful! Changing this at runtime can cause any problem! static void _setMemoryAllocator( MemoryAllocatorInterface* malloc ) { alloc = malloc; } static MemoryAllocatorInterface* _getMemoryAllocator() { return alloc; } private: static MemoryAllocatorInterface* alloc; }; } } ///////pure allocator inline void* eVolve_malloc(unsigned int size) { return eVolve::Core::MemoryAllocatorWrapper::_getMemoryAllocator()->malloc( size ); } inline void* eVolve_realloc(void* memory, unsigned int size) { return eVolve::Core::MemoryAllocatorWrapper::_getMemoryAllocator()->realloc( memory,size ); } ///mono-new inline void* operator new(unsigned int size) { return eVolve::Core::MemoryAllocatorWrapper::_getMemoryAllocator()->instance(size); } ///array new inline void* operator new[](unsigned int size) { return eVolve::Core::MemoryAllocatorWrapper::_getMemoryAllocator()->instance(size); } ///mono-EVOLVE_DELETE inline void operator EVOLVE_DELETE(void* memory) { eVolve::Core::MemoryAllocatorWrapper::_getMemoryAllocator()->free(memory); } ///array EVOLVE_DELETE inline void operator EVOLVE_DELETE[](void* memory) { eVolve::Core::MemoryAllocatorWrapper::_getMemoryAllocator()->free(memory); } #endif #endif tuttavia il problema è che il C++ mi obbliga a dichiarare new nel global namespace, ma così il new originale va perso! In sostanza, io voglio usare il custom allocator SOLO quando lo chiamo esplicitamente, e voglio lasciare new al suo posto... ma voglio anche che EVOLVE_NEW abbia le stesse prerogative, come la sintassi senza parentesi, l'autocast e la chiamata al costruttore. In più la classe wrapper è bruttina, è quanto di meno OOP possa esistere ![]() Conscio dell'orrendezza, mi sapreste consigliare qualcosa per rimediare a questi problemi? |
![]() |
![]() |
![]() |
#2 |
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
due idee.
1 - Se l'uso della malloc standard piuttosto che quella "furba" lo vuoi fare in base alla classe, allora ti conviene definire l'operatore new in classe base e poi derivarla Codice:
struct CustomAllocated { static void* operator new (size_t size) { // ritorna memoria allocata con l'allocatore custom } static void operator delete(void* p) { // libera la memoria con la corrispondente free } }; class Foo: public CustomAllocated { /* ... */ }; Codice:
Foo* x = new Foo();
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele Ultima modifica di marco.r : 06-11-2008 alle 18:00. |
![]() |
![]() |
![]() |
#3 |
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Se vuoi decidere a runtime quale usare il discorso e' piu' complicato, ma si puo' comunque fare.
Un esempio semplice e' Codice:
struct CA {}; CA ca; void* operator new (size_t size, CA ) { return my_malloc(size); } void operator delete(void* p, CA ) { my_free(p); } Codice:
int* x = new int(); Codice:
int* x = new (ca) int(); Puoi anche fare cose piu' evolute, come fare piu' allocatori specifici per una classe, oppure passare un oggetto non idiota come argomento della new, ad esempio l'arena da cui attingere memoria etc.
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
![]() |
![]() |
![]() |
#4 |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Gegno
![]() il secondo approccio è quello che fa per me, dato che già uso il mio bravo define... basta overloadare con un bel bool, anche se temo che questo crei un minimo di overhead sul new normale: Codice:
#define EVOLVE_NEW new(true) #define EVOLVE_DELETE delete(true) ![]() Che poi, ho spesso usato malloc con delete, nedalloc con free, e non cambia niente nel 90% dei casi... Provo e riferisco ![]() EDIT: Provato, e new funge ![]() Tuttavia delete in quella maniera non va, mi dice "impossibile cancellare un parametro che non è pointer", riferendosi sicuramente al bool. Tuttavia la sintassi dovrebbe essere "delete(true) memory" no? EDIT2: Ok, tagliato la testa al C++: ora il custom delete non è più un operatore ma una funzione normale. Il corrispettivo ad EVOLVE_NEW mem sarà quindi EVOLVE_DELETE(mem). Tanto delete è banale e già assomiglia ad una funzione ![]() Ultima modifica di Tommo : 06-11-2008 alle 21:58. |
![]() |
![]() |
![]() |
#5 |
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
hai controllato l'effettiva occupazione di memoria virtuale? la... "malloc di Windows" come la chiami tu, è più lenta perché non chiama direttamente HeapAlloc, ma usa un meccanismo che riduce la frammentazione della memoria virtuale. prova a fare un benchmark in cui allochi una marea di blocchi delle dimensioni più svariate e le allocazioni sono "interleaved" da deallocazioni: secondo me la "malloc di Windows" darà risultati migliori in termini di occupazione di memoria.
|
![]() |
![]() |
![]() |
#6 | |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Quote:
E qui si parla di vantaggi molto maggiori, sulla carta. In ogni caso, se avessi letto prima l'homepage di quell'allocator, avresti visto che è più veloce proprio perchè usa un meccanismo di deframmentazione migliore di quello del malloc di win32. Che si, è diverso da quello di Linux. Peccato che non riesca a provare, perchè Lua mi va in heap corruption e non ho idea del perchè ![]() Ultima modifica di Tommo : 07-11-2008 alle 14:23. |
|
![]() |
![]() |
![]() |
#7 | ||
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
inoltre se non ti interessa l'occupazione di memoria è inutile che usi malloc: usa HeapAlloc e otterrai una velocità maggiore di qualunque malloc, per definizione. Quote:
|
||
![]() |
![]() |
![]() |
#8 | |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Quote:
![]() Cmq grazie per la chiarificazione sul "malloc di win32", per quanto ormai se si programma per win32 VC++ ha il dominio assoluto. Domandine: ma Windows che ruolo ha nell'allocazione della memoria? Di certo non lascia fare come vuole al programma. E, memcpy, memcmp, memmove e simili sono compatibili con tutti gli allocators o solamente con ::malloc e ::free? Grazzie mille ![]() |
|
![]() |
![]() |
![]() |
#9 | |||
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
Quote:
Quote:
Codice:
Foo* x = ... operator delete (x,true);
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
|||
![]() |
![]() |
![]() |
#10 |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Quindi non c'è modo di cavarsela col define come new... vabè tanto delete sta bene come funzione.
Cmq sto in fase di rosik perchè finalmente funziona tutto ma... il vantaggio di fps è davvero aleatorio! Viaggia sui 190 quando il default sta a 200, mentre sta sugli 80 quando il default oscilla fra 40 e 70. In debug però ha la cattva abitudine di far frullare il disco, e rende l'applicazione più laggosa... almeno dai primi test che ho fatto. In release invece va una crema, rimanendo fisso a 62 fps con qualsiasi scena, dato che c'è il FPS lock a 60. Ma lì il default faceva 60... tuttavia col primo sembra essere leggermente più scorrevole. Conclusioni: è stato molto educativa come cosa, ma il vantaggio in applicazioni che non smuovono tonnellate di memoria è praticamente nullo. Al massimo ho 1 fps di più, un pò di responsività e 0.5 s di meno durante il loading. Tutta la velocità se la mangiano allegramente la grafica e i calcoli di gioco... In finale, pare fatto apposta per i contests ![]() Ultima modifica di Tommo : 07-11-2008 alle 16:13. |
![]() |
![]() |
![]() |
#11 | ||
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
quello che non mi sembra ti sia chiaro è che la malloc non è una funzione API Win32 offerta dal sistema operativo, ma è una funzione del runtime dello specifico compilatore: di HeapAlloc ce n'è una su tutto Windows, di malloc ce n'è una per ogni compilatore.* in generale considera che un programma Win32 non ha nessun modo di allocare dinamicamente memoria al di fuori di VirtualAlloc(Ex) e HeapAlloc, il che significa che qualunque compilatore (incluso quello Microsoft) deve per forza offrire una implementazione di malloc basata su uno di quei due set di API. in generale le malloc sono basate su HeapAlloc, e per questo motivo io prima ti dicevo che se vuoi il massimo della velocità e non ti interessa l'occupazione di memoria basta che usi HeapAlloc (e magari allarghi il limite del working set ![]() *fatta eccezione per i compilatori che decidono comunque di usare la malloc del (ed in generale tutto il) runtime di Visual C++; ciò è possibile grazie al fatto che tale runtime è distribuito con Windows e quindi è presente su tutte le macchine. può capitare a volte che un programma richieda una versione del runtime di Visual C++ non presente sulla macchina, in tal caso il programma deve essere distribuito assieme al redistributable della versione di Visual C++ con cui è stato scritto. Quote:
tra l'altro quelle routines non si debbono per forza usare su blocchi di memoria allocati nell'heap: se le usi su un array allocato sullo stack o nel segmento dati non fanno una piega, overflow/underflow a parte. |
||
![]() |
![]() |
![]() |
#12 | ||
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
Quote:
|
||
![]() |
![]() |
![]() |
#13 | |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Quote:
![]() Cmq non me la sento di usare HeapAlloc, mi pare un tantino troppo di basso livello, per quanto basterebbe implementare la classe interfaccia per usarlo ![]() Cmq avevo già fatto il ragionamento della grafica, ma mi aspettavo comunque vantaggi percepibili nel mio gioco (nel link in firma): dato che raggiunge agevolmente i 280fps sul mio pc, non è sicuramente GPU limited... il grosso del peso lo fanno la routine di creazione degli oggetti, l'ai dei tanti omini, e PhysX. Tuttavia son tutti algoritmi che incidono pochissimo sulla memoria, per cui il tempo di allocazione è trascurabile. Certo, se deciderò di fare GTA V lo troverò di certo più utile ![]() |
|
![]() |
![]() |
![]() |
#14 |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Upperello...
Tutto sembrava funzionare, ma sono iniziati a sorgere strani bugs: Codice:
inline void operator delete(void* memory, bool) { eVolve::Core::MemoryAllocatorWrapper::_getMemoryAllocator()->free(memory,false); } #define EVOLVE_DELETE(T) operator delete (T,true) EVOLVE_DELETE(mem); In effetti lo fa, ma manca tutto il "contorno"! Cioè, non chiama il distruttore sugli oggetti che va ad deallocare, causando ogni genere di bugs. Suppongo che sia perchè chiamando direttamente "operator delete" si impedisce alla runtime di gestirsi tutti i suoi meccanismi nascosti... come faccio allora a chiamare esplicitamente questo maledetto distruttore? A sto punto voglio vederla finita sta cosa ![]() EDIT: Per la precisione, non chiama i distruttori della classe Super, ma solo l'ultimo. A volte. Doh ![]() Ultima modifica di Tommo : 08-11-2008 alle 16:06. |
![]() |
![]() |
![]() |
#16 |
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Non ho mai avuto necessita' pratica di usare allocatori diversi per una stessa classe, (a meno che non fosse una arena) per cui vado a spanne; mi sembra comunque che chiamate esplicite a operator delete semplicemente chiamino la funzione. Se hai sottomano lo Stroustrup se non ricordo male c'e' una sezione a riguardo, pure abbastanza dettagliata, che dovrebbe illuminarti.
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
![]() |
![]() |
![]() |
Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 00:29.