L’utilizzo dell’auto “C ++ 11” può migliorare le prestazioni?

Posso capire perché il tipo di auto in C ++ 11 migliora la correttezza e la manutenibilità. Ho letto che può anche migliorare le prestazioni ( Almost Always Auto di Herb Sutter), ma mi manca una buona spiegazione.

  • In che modo è ansible migliorare auto le prestazioni?
  • Qualcuno può dare un esempio?

auto può aiutare le prestazioni evitando conversioni implicite silenziose . Un esempio che trovo convincente è il seguente.

 std::map m; // ... for (std::pair const& item : m) { // do stuff } 

Vedi l’errore? Eccoci qui, pensando di prendere elegantemente ogni elemento della mappa con riferimento const e utilizzando la nuova gamma -per espressione per rendere chiaro il nostro intento, ma in realtà stiamo copiando ogni elemento. Questo perché std::map::value_type è std::pair , non std::pair . Quindi, quando abbiamo (implicitamente):

 std::pair const& item = *iter; 

Invece di prendere un riferimento a un object esistente e lasciarlo a questo, dobbiamo fare una conversione di tipo. È consentito prendere un riferimento const a un object (o temporaneo) di un tipo diverso purché sia ​​disponibile una conversione implicita, ad esempio:

 int const& i = 2.0; // perfectly OK 

La conversione del tipo è una conversione implicita consentita per lo stesso motivo per cui puoi convertire una const Key in una Key , ma dobbiamo costruirci un temporaneo del nuovo tipo per consentirlo. Quindi, effettivamente il nostro ciclo fa:

 std::pair __tmp = *iter; // construct a temporary of the correct type std::pair const& item = __tmp; // then, take a reference to it 

(Naturalmente, in realtà non esiste un object __tmp , è solo lì per l’illustrazione, in realtà il temporaneo senza nome è legato item per la sua durata).

Sto cambiando per:

 for (auto const& item : m) { // do stuff } 

ci hai appena salvato una tonnellata di copie – ora il tipo di riferimento corrisponde al tipo di inizializzatore, quindi non è necessario alcun intervento temporaneo o conversione, possiamo solo fare un riferimento diretto.

Poiché l’ auto deduce il tipo di espressione di inizializzazione, non è implicata alcuna conversione di tipo. Combinato con algoritmi basati su modelli, ciò significa che è ansible ottenere un calcolo più diretto rispetto a se si dovesse creare un tipo da soli, specialmente quando si ha a che fare con espressioni il cui tipo non è ansible nominare!

Un tipico esempio viene da (ab) usando la std::function

 std::function cmp1 = std::bind(f, _2, 10, _1); // bad auto cmp2 = std::bind(f, _2, 10, _1); // good auto cmp3 = [](T a, T b){ return f(b, 10, a); }; // also good std::stable_partition(begin(x), end(x), cmp?); 

Con cmp2 e cmp3 , l’intero algoritmo può cmp3 la chiamata di confronto, mentre se costruisci un object std::function , non solo la chiamata non può essere inline, ma devi anche passare attraverso la ricerca polimorfica all’interno del tipo cancellato della funzione wrapper.

Un’altra variante su questo tema è che puoi dire:

 auto && f = MakeAThing(); 

Questo è sempre un riferimento, associato al valore dell’espressione di chiamata di funzione e non costruisce mai alcun object aggiuntivo. Se non conoscessi il tipo di valore restituito, potresti essere costretto a build un nuovo object (forse come temporaneo) tramite qualcosa come T && f = MakeAThing() . (Inoltre, auto && funziona anche quando il tipo restituito non è mobile e il valore restituito è un valore.)

Ci sono due categorie.

auto può evitare la cancellazione del tipo. Ci sono tipi innominabili (come lambdas) e tipi quasi innominabili (come il risultato di std::bind o altri modelli di espressioni simili).

Senza auto , si finisce per dover digitare cancellare i dati in qualcosa come std::function . Il tipo di cancellazione ha dei costi.

 std::function task1 = []{std::cout << "hello";}; auto task2 = []{std::cout << " world\n";}; 

task1 ha un overhead per la cancellazione dei tipi, una ansible allocazione dell'heap, difficoltà nell'inlining e overhead di invocazione della tabella delle funzioni virtuali. task2 non ne ha. Lambda ha bisogno di auto o altre forms di deduzione di tipo per memorizzare senza cancellazione di tipo; altri tipi possono essere così complessi da averne solo bisogno nella pratica.

Secondo, puoi sbagliare i tipi. In alcuni casi, il tipo sbagliato funzionerà apparentemente perfettamente, ma causerà una copia.

 Foo const& f = expression(); 

compilerà if expression() restituisce Bar const& o Bar o anche Bar& , dove Foo può essere costruito da Bar . Verrà creato un Foo temporaneo, quindi associato a f , e la sua durata verrà estesa fino alla scomparsa di f .

Il programmatore potrebbe aver inteso Bar const& f e non è destinato a fare una copia lì, ma una copia è fatta a prescindere.

L'esempio più comune è il tipo di *std::map::const_iterator , che è std::pair const& non std::pair const& , ma l'errore è una categoria di errori che costano silenziosamente le prestazioni. È ansible build una std::pair da una std::pair . (La chiave su una mappa è const, perché modificarla è una ctriggers idea)

Sia @Barry che @KerrekSB hanno illustrato questi due principi nelle loro risposte. Questo è semplicemente un tentativo di evidenziare i due problemi in una sola risposta, con una formulazione che mira al problema piuttosto che essere centrato sull'esempio.

Le tre risposte esistenti forniscono esempi in cui l’utilizzo dell’aiuto auto “rende meno probabile la pessimizzazione involontaria” rendendolo effettivamente “migliorare le prestazioni”.

C’è un rovescio della medaglia. L’utilizzo di auto con oggetti con operatori che non restituiscono l’object di base può comportare un codice errato (ancora compilabile e eseguibile). Ad esempio, questa domanda chiede come usare auto dato risultati diversi (errati) usando la libreria Eigen, cioè le seguenti linee

 const auto resAuto = Ha + Vector3(0.,0.,j * 2.567); const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567); std::cout << "resAuto = " << resAuto <Il metodo Scalable Contains per LINQ su un back-end SQL
  • Cosa rende le chiamate JNI lente?
  • Le prestazioni di MongoDB sulle query di aggregazione
  • Perché questo codice F # è così lento?
  • Strumenti per analizzare le prestazioni di un programma Haskell
  • Spark sql query vs funzioni dataframe
  • In java, è più efficiente usare byte o short invece di int e float invece di double?
  • Perché il mio programma rallenta quando esegui il ciclo su esattamente 8192 elementi?