2015-05-02 21 views
8

Nel suo discorso "Efficiency with algorithms, Performance with data structures", Chandler Carruth parla della necessità di un modello di allocatore migliore in C++. L'attuale modello di allocatore invade il sistema di tipi e rende quasi impossibile lavorare in molti progetti a causa di ciò. D'altra parte, lo Bloomberg allocator model non invade il sistema di tipi ma si basa su chiamate di funzioni virtuali che rendono impossibile al compilatore 'vedere' l'allocazione e ottimizzarla. Nel suo discorso parla dei compilatori deduplicando l'allocazione di memoria (1:06:47).Allocazione memoria ottimizzata via dai compilatori

Mi ci è voluto un po 'di tempo per trovare alcuni esempi di ottimizzazione dell'allocazione della memoria, ma ho trovato questo esempio di codice, compilato in clang, che ottimizza l'allocazione di memoria e restituisce solo 1000000 senza allocare nulla.

template<typename T> 
T* create() { return new T(); } 

int main() { 
    auto result = 0; 
    for (auto i = 0; i < 1000000; ++i) { 
     result += (create<int>() != nullptr); 
    } 

    return result; 
} 

Il seguente documento http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3664.html dice anche che l'assegnazione potrebbe essere fusa in compilatori e sembra suggerire che alcuni compilatori già fanno questo genere di cose.

Dato che sono molto interessato alle strategie per l'allocazione di memoria efficiente, voglio davvero capire perché Chandler Carruth è contro le chiamate virtuali nel modello di Bloomberg. L'esempio sopra mostra chiaramente che clang ottimizza le cose quando può vedere l'allocazione.

  1. mi piacerebbe avere un "codice di vita reale", dove questa ottimizzazione è utile e fatto da qualsiasi compilatore corrente
  2. Avete qualche esempio di codice in cui allocazione diversa sono fusi da Anu compilatore attuale?
  3. Capisci cosa significa Chandler Carruth quando dice che i compilatori possono "deduplicare" la tua allocazione nel suo discorso alle 1:06:47?
+1

La funzione 'create' è una funzione con effetti collaterali in fase di esecuzione, il compilatore non può conoscere i risultati di tali effetti collaterali in fase di esecuzione in fase di compilazione. Quello che * può * fare è vedere che la memoria allocata non è realmente usata da nessuna parte, il che potrebbe essere una ragione per cui potrebbe ottimizzare le allocazioni, ma direi che è sbagliato solo perché il compilatore non ha modo di predire i risultati alla compilazione -tempo. –

+1

@Joachim: questo esempio è stato discusso qui http://stackoverflow.com/questions/25668420/clang-vs-gcc-optimization-including-operator-new. Secondo il n3664 di cui ho parlato nel mio post, lo standard non è chiaro se è permesso o meno. Ma sembra dire che molti compilatori già lo fanno. – InsideLoop

risposta

2

Ho trovato questo sorprendente esempio che risponde al primo punto della domanda iniziale. Entrambi i punti 2 e 3 non hanno ancora alcuna risposta.

#include <iostream> 
#include <vector> 
#include <chrono> 

std::vector<double> f_val(std::size_t i, std::size_t n) { 
    auto v = std::vector<double>(n); 
    for (std::size_t k = 0; k < v.size(); ++k) { 
     v[k] = static_cast<double>(k + i); 
    } 
    return v; 
} 

void f_ref(std::size_t i, std::vector<double>& v) { 
    for (std::size_t k = 0; k < v.size(); ++k) { 
     v[k] = static_cast<double>(k + i); 
    } 
} 

int main (int argc, char const *argv[]) { 
    const auto n = std::size_t{10}; 
    const auto nb_loops = std::size_t{300000000}; 

    // Begin: Zone 1 
    { 
     auto v = std::vector<double>(n, 0.0); 
     auto start_time = std::chrono::high_resolution_clock::now(); 
     for (std::size_t i = 0; i < nb_loops; ++i) { 
      auto w = f_val(i, n); 
      for (std::size_t k = 0; k < v.size(); ++k) { 
       v[k] += w[k]; 
      } 
     } 
     auto end_time = std::chrono::high_resolution_clock::now(); 
     auto time = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count(); 
     std::cout << time << std::endl; 
     std::cout << v[0] << " " << v[n - 1] << std::endl; 
    } 
    // End: Zone 1 

    { 
     auto v = std::vector<double>(n, 0.0); 
     auto w = std::vector<double>(n); 
     auto start_time = std::chrono::high_resolution_clock::now(); 
     for (std::size_t i = 0; i < nb_loops; ++i) { 
      f_ref(i, w); 
      for (std::size_t k = 0; k < v.size(); ++k) { 
       v[k] += w[k]; 
      } 
     } 
     auto end_time = std::chrono::high_resolution_clock::now(); 
     auto time = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count(); 
     std::cout << time << std::endl; 
     std::cout << v[0] << " " << v[n - 1] << std::endl; 
    } 

    return 0; 
} 

dove non si verifica una singola allocazione di memoria nel ciclo for con f_val. Questo succede solo con Clang (Gcc e icpc falliscono entrambi) e quando si costruisce un esempio leggermente più complicato, l'ottimizzazione non viene eseguita.

+0

"nel ciclo for con f_val": si intende il ciclo for all'interno di f_val o il ciclo for che chiama f_val? – TonyK

+0

@TonyK: nel ciclo for che chiama f_val. Nella zona 1, sembra che ci sia solo un'allocazione (per v) che mi fa pensare. La mia ipotesi è che la chiamata f_val sia in linea, quindi i k-loop sono fusi e quindi il compilatore rimuove l'allocazione di w che diventa inutile! Ho controllato l'assemblea. – InsideLoop