significato - overload operator c++



Quanto costa il sovraccarico dei puntatori intelligenti rispetto ai normali puntatori in C++? (3)

Quanto costa il sovraccarico dei puntatori intelligenti rispetto ai normali puntatori in C ++ 11? In altre parole, il mio codice sarà più lento se uso puntatori intelligenti e, in caso affermativo, quanto più lento?

Nello specifico, sto chiedendo di C ++ 11 std::shared_ptr e std::unique_ptr .

Ovviamente, le cose buttate giù dallo stack saranno più grandi (almeno credo), perché un puntatore intelligente deve anche memorizzare il suo stato interno (conteggio dei riferimenti, ecc.), La domanda è davvero, quanto andrà a finire influire sulla mia prestazione, se non del tutto?

Ad esempio, restituisco un puntatore intelligente da una funzione anziché un puntatore normale:

std::shared_ptr<const Value> getValue();
// versus
const Value *getValue();

Oppure, ad esempio, quando una delle mie funzioni accetta un puntatore intelligente come parametro anziché un puntatore normale:

void setValue(std::shared_ptr<const Value> val);
// versus
void setValue(const Value *val);

https://src-bin.com


Answer #1

Come con tutte le prestazioni del codice, l'unico mezzo veramente affidabile per ottenere informazioni rigide è misurare e / o ispezionare il codice della macchina.

Detto questo, il ragionamento semplice lo dice

  • Ci si può aspettare un po 'di overhead nelle build di debug, poiché ad es. operator-> deve essere eseguito come una chiamata di funzione in modo che tu possa intervenire (ciò a sua volta a causa della mancanza generale di supporto per marcare classi e funzioni come non-debug).

  • Per shared_ptr si può aspettare un overhead nella creazione iniziale, poiché ciò implica l'allocazione dinamica di un blocco di controllo, e l'allocazione dinamica è molto più lenta di qualsiasi altra operazione di base in C ++ (usare make_shared quando è praticamente possibile, per minimizzare l'overhead).

  • Anche per shared_ptr c'è un po 'di overhead minimo nel mantenere un conteggio dei riferimenti, ad esempio quando si passa un shared_ptr per valore, ma non esiste un overhead per unique_ptr .

Tenendo presente il primo punto in testa, quando misuri, fallo sia per il debug che per il rilascio delle build.

Il comitato internazionale di standardizzazione del C ++ ha pubblicato un rapporto tecnico sulle prestazioni , ma questo era nel 2006, prima che unique_ptr e shared_ptr fossero aggiunti alla libreria standard. Tuttavia, i puntatori intelligenti erano a quel punto vecchi, quindi il rapporto ha preso in considerazione anche questo. Citando la parte rilevante:

"Se l'accesso a un valore tramite un puntatore intelligente banale è significativamente più lento dell'accesso tramite un puntatore ordinario, il compilatore gestisce in modo inefficiente l'astrazione. In passato, la maggior parte dei compilatori presentava sanzioni significative per l'astrazione e diversi compilatori correnti continuano a farlo. Tuttavia, almeno due compilatori sono stati segnalati per avere pene di astrazione inferiori all'1% e un'altra una penalità del 3%, quindi eliminare questo tipo di sovraccarico è ben all'interno dello stato dell'arte "

Come ipotesi informata, il "pozzo nello stato dell'arte" è stato realizzato con i compilatori più popolari oggi, a partire dall'inizio del 2014.


Answer #2

La mia risposta è diversa dalle altre e mi chiedo davvero se abbiano mai profilato il codice.

shared_ptr ha un overhead significativo per la creazione a causa della sua allocazione di memoria per il blocco di controllo (che mantiene il contatore ref e una lista puntatori a tutti i riferimenti deboli). Ha anche un sovraccarico di memoria enorme a causa di questo e il fatto che std :: shared_ptr è sempre una tupla a 2 puntatori (uno per l'oggetto, uno per il blocco di controllo).

Se si passa un parametro shared_pointer a una funzione come parametro di valore, allora sarà almeno 10 volte più lento di una normale chiamata e creerà molti codici nel segmento di codice per lo srotolamento dello stack. Se lo si passa per riferimento si ottiene un'ulteriore indiretta che può essere anche peggiore in termini di prestazioni.

Ecco perché non dovresti farlo a meno che la funzione non sia realmente coinvolta nella gestione della proprietà. Altrimenti usa "shared_ptr.get ()". Non è progettato per garantire che il tuo oggetto non venga ucciso durante una normale chiamata di funzione.

Se impazzisci e usa shared_ptr su oggetti piccoli come un albero di sintassi astratto in un compilatore o su piccoli nodi in qualsiasi altra struttura grafica, vedrai un'enorme calo della perfomance e un enorme aumento di memoria. Ho visto un sistema di parser che è stato riscritto poco dopo il lancio di C ++ 14 sul mercato e prima che il programmatore imparasse a usare correttamente i puntatori intelligenti. La riscrittura era di una grandezza più lenta del vecchio codice.

Non è un proiettile argentato e neanche i puntatori grezzi sono cattivi per definizione. I cattivi programmatori sono cattivi e il cattivo design è cattivo. Progettare con cura, progettare pensando chiaramente alla proprietà e provare a utilizzare shared_ptr principalmente sul limite dell'API del sottosistema.

Se vuoi saperne di più puoi guardare Nicolai M. Josuttis parlare bene di "Il vero prezzo dei puntatori condivisi in C ++" https://vimeo.com/131189627
Approfondisce i dettagli di implementazione e l'architettura della CPU per le barriere di scrittura, i blocchi atomici ecc. Una volta ascoltato, non parlerai mai di questa funzione essendo economica. Se vuoi solo una prova della magnitudine più lenta, salta i primi 48 minuti e guardalo mentre esegue un codice di esempio che viene eseguito fino a 180 volte più lento (compilato con -O3) quando si usa il puntatore condiviso ovunque.


Answer #3

In altre parole, il mio codice sarà più lento se uso puntatori intelligenti e, in caso affermativo, quanto più lento?

Più lentamente? Molto probabilmente no, a meno che non si stia creando un indice enorme usando shared_ptrs e non si abbia abbastanza memoria al punto che il computer inizi a raggrinzirsi, come una vecchia signora precipitata a terra da una forza insopportabile da lontano.

Ciò che renderebbe il tuo codice più lento è la ricerca lenta, l'elaborazione loop non necessaria, le enormi copie di dati e molte operazioni di scrittura su disco (come centinaia).

I vantaggi di un puntatore intelligente sono tutti correlati alla gestione. Ma l'overhead è necessario? Questo dipende dalla tua implementazione. Diciamo che stai iterando su un array di 3 fasi, ogni fase ha una matrice di 1024 elementi. La creazione di uno smart_ptr per questo processo potrebbe essere eccessivo, poiché una volta completata l'iterazione saprai che devi cancellarlo. In questo modo è possibile ottenere memoria aggiuntiva non utilizzando uno smart_ptr ...

Ma vuoi davvero farlo?

Una perdita di memoria singola potrebbe far sì che il tuo prodotto abbia un punto di errore nel tempo (diciamo che il tuo programma perde 4 megabyte ogni ora, ci vorranno mesi per rompere un computer, tuttavia si romperà, lo sai perché c'è la perdita) .

È come dire "il tuo software è garantito per 3 mesi, quindi chiamami per il servizio".

Quindi alla fine è davvero una questione di ... puoi gestire questo rischio? utilizzare un puntatore raw per gestire l'indicizzazione su centinaia di oggetti diversi vale la pena perdere il controllo della memoria.

Se la risposta è sì, quindi utilizzare un puntatore raw.

Se non vuoi nemmeno prenderlo in considerazione, uno smart_ptr è una soluzione valida, valida e straordinaria.





smart-pointers