Prestazioni di dynamic_cast?

Prima di leggere la domanda:
Questa domanda non riguarda quanto sia utile usare dynamic_cast . È solo per le sue prestazioni.

Recentemente ho sviluppato un progetto in cui dynamic_cast viene utilizzato molto.
Quando discutiamo con i colleghi, quasi tutti dicono che dynamic_cast non dovrebbe essere usato a causa delle sue cattive prestazioni (questi sono collaboratori che hanno background diversi e in alcuni casi non si conoscono. Sto lavorando in una grande azienda )

Ho deciso di testare le prestazioni di questo metodo invece di crederci.

È stato utilizzato il seguente codice:

 ptime firstValue( microsec_clock::local_time() ); ChildObject* castedObject = dynamic_cast(parentObject); ptime secondValue( microsec_clock::local_time() ); time_duration diff = secondValue - firstValue; std::cout << "Cast1 lasts:\t" << diff.fractional_seconds() << " microsec" << std::endl; 

Il codice sopra utilizza metodi di boost::date_time su Linux per ottenere valori utilizzabili.
Ho eseguito 3 dynamic_cast in un’unica esecuzione, il codice per misurarli è lo stesso.

I risultati di 1 esecuzione erano i seguenti:
Cast1 dura: 74 microsec
Cast2 dura: 2 microsec
Cast3 dura: 1 microsec

Il primo cast richiedeva sempre 74-111 microsec, i seguenti cast nella stessa esecuzione richiedevano 1-3 microsec.

Quindi finalmente le mie domande:
dynamic_cast davvero male?
Secondo il test, non lo è. Il mio codice test è corretto?
Perché così tanti sviluppatori pensano che sia lento se non lo è?

In primo luogo, è necessario misurare le prestazioni molto più di poche iterazioni, poiché i risultati saranno dominati dalla risoluzione del timer. Prova ad es. 1 milione +, per build un’immagine rappresentativa. Inoltre, questo risultato non ha senso se non lo paragoni a qualcosa, cioè a fare l’equivalente ma senza il lancio dinamico.

In secondo luogo, è necessario assicurarsi che il compilatore non ti dia risultati falsi ottimizzando via più cast dinamici sullo stesso puntatore (quindi usa un ciclo, ma usa un puntatore di input diverso ogni volta).

La trasmissione dynamic sarà più lenta, poiché è necessario accedere alla tabella RTTI (informazioni sul tipo di esecuzione) per l’object e verificare che il cast sia valido. Quindi, per usarlo correttamente, dovrai aggiungere un codice di gestione degli errori che controlli se il puntatore restituito è NULL . Tutto ciò richiede cicli.

So che non volevi parlare di questo, ma “un design in cui dynamic_cast è usato molto” è probabilmente un indicatore del fatto che stai facendo qualcosa di sbagliato …

Le prestazioni non hanno senso senza confrontare funzionalità equivalenti. La maggior parte delle persone dice che dynamic_cast è lento senza paragonarsi a un comportamento equivalente. Chiamali su questo. Dirlo in un altro modo:

Se “funziona” non è un requisito, posso scrivere codice che fallisce più velocemente del tuo.

Esistono vari modi per implementare dynamic_cast e alcuni sono più veloci di altri. Stroustrup ha pubblicato un articolo sull’utilizzo dei primi per migliorare dynamic_cast , ad esempio. Sfortunatamente è inusuale controllare il modo in cui il compilatore implementa il cast, ma se le prestazioni sono davvero importanti per te, allora hai il controllo su quale compilatore usi.

Tuttavia, non utilizzare dynamic_cast sarà sempre più veloce di usarlo, ma se non hai effettivamente bisogno di dynamic_cast, non usarlo! Se hai bisogno di una ricerca dynamic, allora ci sarà un sovraccarico e potrai quindi confrontare varie strategie.

Ecco alcuni punti di riferimento:
http://tinodesdriksen.com/2010/04/14/cpp-dynamic-cast-performance/
http://www.nerdblog.com/2006/12/how-slow-is-dynamiccast.html

Secondo loro, dynamic_cast è 5-30 volte più lento di reinterpret_cast, e l’alternativa migliore si comporta quasi come reinterpret_cast.

Citerò le conclusioni del primo articolo:

  • dynamic_cast è lento per tutto tranne che per il casting del tipo base; quel particolare cast è ottimizzato
  • il livello di ereditarietà ha un grande impatto su dynamic_cast
  • membro variabile + reinterpret_cast è il modo più veloce e affidabile
    determinare il tipo; tuttavia, questo ha un sovraccarico di manutenzione molto più alto
    durante la codifica

I numeri assoluti sono dell’ordine di 100 ns per un singolo cast. Valori come 74 msec non sembrano vicini alla realtà.

Mi spiace dirlo, ma il tuo test è praticamente inutile per determinare se il cast è lento o no. La risoluzione del microsecondo non è mai abbastanza buona. Stiamo parlando di un’operazione che, anche nel peggiore dei casi, non dovrebbe richiedere più di 100 tick dell’orologio o meno di 50 nanosecondi su un PC tipico.

Non c’è dubbio che il cast dinamico sarà più lento di un cast statico o di un cast reinterpretato, perché, a livello di assembly, gli ultimi due ammonteranno a un compito (molto veloce, l’ordine di 1 tick del clock), e il cast dinamico richiede il codice per andare e ispezionare l’object per determinare il suo vero tipo.

Non posso dire fuori mano quanto sia lento, che probabilmente varierebbe dal compilatore al compilatore, avrei bisogno di vedere il codice assembly generato per quella linea di codice. Ma, come ho detto, 50 nanosecondi per chiamata è il limite massimo di ciò che si aspetta essere ragionevole.