Esistono esempi reali che dimostrano un ragionevole miglioramento delle prestazioni usando la semantica del movimento?

3

(Ho posto una domanda simile su SO ma sfortunatamente potrebbe non essere corretto, quindi lo metto anche qui, ti preghiamo gentilmente di indicare se pensi che sia un duplicato.)

Ho sentito molte parole sulla traslazione semantica (essenzialmente rvalue) introdotto in C ++ 11. In teoria , dovrebbe comportare un notevole miglioramento delle prestazioni dovuto al fatto che evita inutili copie .

Tuttavia, ci sono già stati qualche ottimizzazione per codice legacy durante la compilazione per gestire le copie temporanee inefficienti, come come:

Inoltre, per le strutture dati utilizzate frequentemente, alcune librerie standard C ++ utilizzano ottimizzazioni speciali (ad esempio, ottimizzazione di piccole stringhe per std::string ).

Ancora più importante, anche se alcune parti del codice precedente sono veramente inefficienti, non comportano molta latenza poiché sono

  • non viene spesso richiamato
  • i computer moderni hanno abbastanza memorie fisiche per loro

Quindi mi chiedo: ci sono esempi del mondo reale che accelerano notevolmente le prestazioni quando si utilizza la moderna sintassi C ++ (C ++ 11/14/17), o si migliorano le prestazioni con un ragionevole percentuale (ad esempio, > 10%) generale ?

Mi aspetto che la risposta possa essere una delle 3 categorie:

#include <vector>
using std::vector;
using std::size_t;

size_t const MAX = NNN;  // NNN is specified by -DNNN=xxx option
size_t const NUM = NNN/100;

vector<int> factory(size_t size) {
  vector<int> v;
  for (size_t i = 0u; i < size; ++i) {
    v.push_back(i);
  }
  return v;
}

// Version 1
/// void doubles(vector<int> & v) {
// Version 2
void doubles(vector<int> && v) {
  for (size_t i = 0u; i < v.size(); ++i) {
    v[i] = v[i] * 2;
  }
}

int main() {
// Version 1
/// vector<int> v = factory(MAX);
/// doubles(v);
// Version 2
  doubles(factory(MAX));
}
  1. Alcuni bug / colli di bottiglia delle prestazioni esistenti in repository del mondo reale che possono essere gestiti bene con il C ++ moderno.
  2. Alcuni profiling per un pezzo di codice che mostra il miglioramento.
  3. Iniziare modificando alcuni dei sopra codice banale per aiutarmi a ottenere un esempio che può portare il vantaggio in termini di prestazioni.

E il miglioramento può ancora essere visualizzato con default (es. -O0 ) opzioni di compilazione (quindi -fno-elide-constructors non è consentito durante la compilazione) di gcc or clang or MSVC.

Ho fatto questa domanda perché stavo facendo un sondaggio sull'impatto della semantica sulle prestazioni per i programmi del mondo reale, ma dopo aver provato un codice (banale) e fatto una semplice analisi di base, ho scoperto che semplicemente non riesco a trovare le differenze significative . Quindi, per favore perdonami se lo senti stupido / pedante.

    
posta Hongxu Chen 26.04.2015 - 19:20
fonte

1 risposta

4

Se hai bisogno di una panoramica dei benefici e delle best practice sulla semantica del movimento, guarda alcune delle registrazioni della conferenza sul isocpp sito web .
(In fondo c'è un collegamento alle registrazioni più vecchie.)

Bjarne Stroustrup fornisce un esempio motivante sul suo sito web.

link

Prendi in considerazione l'implementazione tipica di std::swap , assumendo che questo metodo non abbia accesso speciale al tipo .
Il codice di esempio e i commenti sottostanti sono copiati dal link precedente.

template<class T> swap(T& a, T& b)      // "old style swap"
{
    T tmp(a);   // now we have two copies of a
    a = b;      // now we have two copies of b
    b = tmp;    // now we have two copies of tmp (aka a)
} 

Quando vengono creati nuovi oggetti, questo comporta il costo di copiare quell'oggetto. Il più delle volte, ciò implica la copia profonda - non condividere nulla , perché ogni oggetto deve essere preparato per essere indipendentemente modificabile, perché non c'è niente che implichi altrimenti.

Ma in questo esempio, è chiaro che tmp è un temporaneo. Cosa possiamo fare per evitare il costo della copia profonda in questo caso?

Come sottolinea @DocBrown nel commento, i vantaggi della semantica del movimento dipendono da:

  • Lo stile di codifica
  • L'implementazione delle strutture dati utilizzate più pesantemente nel codice

Nella programmazione orientata agli oggetti, esiste un problema contenzioso: copia o condivisione ? (Un altro problema controverso è mutevole o immutabile .)

La maggior parte dei programmi software impiegherà del tempo a copiare cose. Le domande sono:

  • La situazione richiede la copia?
  • C'è un modo più economico di copiare?

Se due o più istanze di codice hanno bisogno di accedere allo stesso oggetto, e se tutte queste istanze promettono che non modificheranno mai l'oggetto (cioè che cambieranno i suoi stati), forse condivideranno il riferimento all'oggetto (tramite puntatore o altro mezzi) può essere sufficiente.

Se una istanza di codice deve fare una copia in modo che l'oggetto possa essere modificato, non trarrà vantaggio dalla maggior parte dello schema "make copy cheap".

A volte è una via di mezzo. Un oggetto ha più proprietà e il codice vuole creare una copia in modo che una o più proprietà possano essere modificate. In questo caso, "make copy cheap" richiederebbe uno per consentire la condivisione di proprietà invariate tra il vecchio e il nuovo oggetto. (Nota: spostare la semantica non abilitare questo. Dico questo perché la semantica del movimento deve affrontare una serie di altri tipi di semantica in competizione .

Il codice C ++ scritto in uno stile C, con il suo uso intensivo di puntatori, potrebbe non vedere alcun vantaggio, poiché tale codice condivide già liberamente qualsiasi struttura dati condividendo i puntatori e lo fa senza molte garanzie sintattiche.

Codice C ++ che implementa già il conteggio dei riferimenti (come Mat della classe OpenCV ), puntatori Microsoft COM ( com_ptr_t ) , ecc., consentono di eseguire più istanze di codice condividere lo stesso pezzo di dati.

Il tipo di codice C ++ che può beneficiare della semantica del movimento è quello che

  1. Principalmente si basano su strutture di dati STL (soprattutto std::vector ),
  2. Utilizza pesantemente la "semantica del valore" (rende gli oggetti immutabili, fa pesantemente le copie degli oggetti, preferisce copiare i valori ai riferimenti di condivisione), e
  3. Affinché i suoi miglioramenti delle prestazioni siano misurabili,
    • Dovrebbe essere un po 'pesante (cioè la quantità di dati e il calcolo dovrebbero essere ragionevolmente grandi per essere misurabili)
    • Non dovrebbe essere dominato da altri tipi di colli di bottiglia (come disco, IO, database, ecc.).

Si potrebbe dire che ciascuno di questi fattori è discutibile, e giustamente.

Esistono programmi C ++ che implementano il proprio conteggio dei riferimenti, schemi di condivisione dei riferimenti, valutazione lazy (su richiesta), operazioni asincrone o promesse future ecc. molto prima di C ++ 11 è stato concepito Questi ambienti di programmazione C ++ hanno scelto una traiettoria che li rende ampiamente indipendenti dalle evoluzioni del C ++. Da una prospettiva storica, potrebbero avere ragione, perché le evoluzioni di C ++ erano apparentemente stagnanti per circa un decennio, dove si pensa che la maggior parte delle innovazioni siano realizzabili con il codice di libreria (come le librerie Boost) senza richiedere modifiche al lingua standard.

    
risposta data 26.04.2015 - 20:10
fonte

Leggi altre domande sui tag