2015-12-22 5 views
14

C++ 14 introdotto "sized" versions of operator delete, cioèCome utilizzerei gli operatori di dimensioni delete/delete [] e perché sono migliori?

void operator delete(void* ptr, std::size_t sz); 

e

void operator delete[](void* ptr, std::size_t sz); 

Leggendo N3536, sembra che gli operatori sono stati introdotti per aumentare le prestazioni. So che l'allocatore tipico utilizzato da operator new "memorizza" la dimensione della memoria di massa da qualche parte, ed è così che il tipico operator delete "sa" quanta memoria deve tornare al negozio gratuito.

Non sono sicuro, tuttavia, perché le versioni "dimensionate" di operator delete siano utili in termini di prestazioni. L'unica cosa che può accelerare le cose è una operazione di lettura in meno per quanto riguarda le dimensioni dal blocco di controllo. Questo è davvero l'unico vantaggio?

In secondo luogo, come posso gestire la versione dell'array? AFAIK, la dimensione della matrice allocata non è semplicemente sizeof(type)*number_elements, ma potrebbero esserci alcuni byte aggiuntivi assegnati poiché l'implementazione potrebbe utilizzare quei byte come byte di controllo. Che "taglia" dovrei passare a operator delete[] in questo caso? Potete fornire un breve esempio di utilizzo?

+1

Re la versione dell'array: questo è il problema del compilatore, non il tuo (si passa a ciò che è stato passato a 'operator new []' per allocare quella memoria). –

+3

Non si usa questa frase dal documento * I moderni allocatori di memoria spesso allocano in categorie di dimensioni e, per ragioni di efficienza dello spazio, non memorizzano la dimensione dell'oggetto vicino all'oggetto. La deallocazione richiede quindi la ricerca dell'archivio delle categorie di dimensioni che contiene l'oggetto. Questa ricerca può essere costosa, in particolare perché le strutture dei dati di ricerca spesso non sono nelle cache di memoria. * Coprite ciò che state chiedendo? –

+0

@ T.C. Ma come posso scoprire cosa è stato distribuito a 'operator new []'? È semplicemente 'sizeof arr'? – vsoftco

risposta

9

Trattare con la tua seconda domanda:

Se presente, l'argomento size std :: size_t deve essere uguale l'argomento size passato alla funzione di allocazione che ha restituito ptr.

Quindi, qualsiasi spazio aggiuntivo che potrebbe essere assegnato è responsabilità della libreria di runtime, non del codice client.

La prima domanda è più difficile da rispondere bene. L'idea primaria è (o almeno sembra essere) che le dimensioni di un blocco spesso non vengano archiviate proprio accanto al blocco stesso. Nella maggior parte dei casi, la dimensione del blocco viene scritta e mai più scritta finché il blocco non viene deallocato. Per evitare che i dati inquinino la cache mentre il blocco è in uso, può essere tenuto separatamente. Quindi, quando si va a deallocare il blocco, la dimensione verrà spesso scaricata sul disco, quindi leggerlo di nuovo è piuttosto lento.

È anche abbastanza comune evitare di memorizzare esplicitamente la dimensione di ogni blocco in modo esplicito. Un allocatore avrà frequentemente pool separati per blocchi di dimensioni diverse (ad es. Potenze da 2 a 16 fino a circa un paio di kilobyte circa). Alloca un blocco (abbastanza grande) dal sistema operativo per ogni pool, quindi assegna all'utente blocchi di quel grande blocco. Quando si restituisce un indirizzo, esso cerca fondamentalmente quell'indirizzo attraverso le diverse dimensioni dei pool per trovare il pool da cui proviene. Se ci sono molte piscine e molti blocchi in ogni piscina, ciò può essere relativamente lento.

L'idea è di evitare entrambe le possibilità. In un caso tipico, le allocazioni/deallocazioni sono più o meno legate allo stack e, quando sono le dimensioni che si stanno allocando, probabilmente si troveranno in una variabile locale. Quando si rilascia un deal, in genere si troverà (o almeno vicino) allo stesso livello dello stack rispetto a dove si è effettuata l'allocazione, in modo che la stessa variabile locale sia facilmente disponibile e probabilmente non verrà distribuita sul disco. (o qualcosa del genere) perché anche altre variabili memorizzate nelle vicinanze sono in uso. Per il modulo non a matrice, la chiamata a ::operator new deriva in genere da un new expression e la chiamata a ::operator delete dal corrispondente delete expression.In questo caso, il codice generato per costruire/distruggere l'oggetto "conosce" la dimensione che richiederà (e distruggerà) basandosi esclusivamente sul tipo di oggetto creato/distrutto.

+0

Un semplice 'delete p' conosce la dimensione dell'allocazione (se il distruttore di' p' non è polimorfico, lo conosce in modo statico), comunque. –

+0

Mi ricorda di [Andrei Alexandrescu's talk at CppCon'15] (https://youtu.be/LIb3L4vKZ7U?list=PLHTh1InhhwT75gykhs7pqcR_uSiG601oh) ... – 5gon12eder

+0

@ 5gon12eder Sì, ho visto quel discorso, e quanto era incazzato per il nuovo/cancella:) – vsoftco

4

Per l'argomento size a C++ 14 operator delete è necessario passare le stesse dimensioni dato a operator new, che è in byte. Ma come hai scoperto è più complicato per gli array. Per il motivo per cui è più complicato, vedi qui: Array placement-new requires unspecified overhead in the buffer?

Quindi, se si esegue questa operazione:

std::string* arr = new std::string[100] 

Può non essere valida per fare questo:

operator delete[](arr, 100 * sizeof(std::string)); # BAD CODE? 

perché l'originale new espressione era non equivalente a:

std::string* arr = new (new char[100 * sizeof(std::string)]) std::string[100]; 

Per quanto riguarda il fatto che l'API delete è migliore, sembra che sia today it is actually not ma la speranza è che alcune librerie standard miglioreranno le prestazioni della deallocazione perché in realtà non memorizzano le dimensioni dell'allocazione accanto a ciascun blocco assegnato (il classico/libro di testo modello). Per ulteriori informazioni, vedere qui: Sized Deallocation Feature In Memory Management in C++1y

E ovviamente la ragione per non memorizzare la dimensione accanto a ogni allocazione è che si tratta di uno spreco di spazio se non ne hai veramente bisogno. Per i programmi che fanno molte piccole allocazioni dinamiche (che sono più popolari di quanto dovrebbero essere!), Questo overhead può essere significativo. Ad esempio, nel costruttore "plain vanilla" std::shared_ptr (anziché in make_shared), un conteggio dei riferimenti viene allocato dinamicamente, quindi se l'allocatore memorizza la dimensione accanto ad esso, potrebbe ingenuamente richiedere un sovraccarico del 25%: un intero "size" per allocatore più four-slot control block. Per non parlare della pressione della memoria: se la dimensione non viene memorizzata accanto al blocco assegnato, si evita il caricamento di una riga dalla memoria sulla deallocazione - l'unica informazione necessaria è data nella chiamata alla funzione (beh, è ​​anche necessario guardare l'arena o la free-list o qualsiasi altra cosa, ma era necessario che, in ogni caso, si continuasse a saltare un carico).

+1

Il blocco di controllo 'shared_ptr' non è solo un intero. –

+0

@ T.C .: buon punto, aggiornato quella parte. –

+0

Il 'posizionamento Array-new richiede un overhead non specificato nel buffer?' La discussione ha alcune cazzate serie, in particolare, riguardo le ** funzioni di allocazione dei posizionamenti non allocative ** (ignora * l'allocazione * per un secondo). Il 'new (ptr) T [n]' non ha mai richiesto un overhead non specificato, ** non alloca, non fa nulla, funge solo da operatore fittizio per la costruzione di oggetti sul posto **. Se c'era un compilatore che aggiungeva quell'overhead, certamente era VC++. – bit2shift

Problemi correlati