Uso una unordered_map come array 3D sparsi (128 x 128 x 128) per inserire valori in una griglia, a condizione che la cella della griglia sia ancora libera.Perché unordered_map "trova + inserisci" più velocemente di "insert + check for success"?
Fino ad ora ho sempre controllato con find() se la cella è libera e se lo è, allora ho aggiunto un elemento usando insert() o emplace(). Ora ho scoperto che posso usare il valore restituito di insert ed emplace per controllare se l'elemento è stato aggiunto o se c'era già un elemento con la stessa chiave all'interno della mappa. Ho pensato che questo avrebbe potuto migliorare le prestazioni in quanto avrei potuto rimuovere completamente l'utilizzo di find.
Come risulta, invece di migliorare le prestazioni inserendo senza trovare, le prestazioni sono effettivamente diminuite e non sono sicuro del perché.
Ho ridotto la mia applicazione a questo esempio in cui i punti vengono generati casualmente e quindi inseriti nella griglia.
#include <unordered_map>
#include <random>
#include <chrono>
#include <iostream>
#include <math.h>
#include <algorithm>
#include <string>
using std::cout;
using std::endl;
using std::chrono::high_resolution_clock;
using std::chrono::milliseconds;
using std::chrono::duration_cast;
using std::unordered_map;
int num_elements = 5'000'000;
void findThenInsert(){
cout << endl << "find and emplace" << endl;
auto start = high_resolution_clock::now();
std::mt19937 gen(123);
std::uniform_real_distribution<> dis(0, 128);
unordered_map<int, int> grid;
int count = 0;
for(int i = 0; i < num_elements; i++){
float x = dis(gen);
float y = dis(gen);
float z = (cos(x*0.1) * sin(x*0.1) + 1.0) * 64.0;
int index = int(x) + int(y) * 128 + int(z) * 128 * 128;
auto it = grid.find(index);
if(it == grid.end()){
grid.emplace(index, count);
count++;
}
}
cout << "elements: " << count << endl;
cout << "load factor: " << grid.load_factor() << endl;
auto end = high_resolution_clock::now();
long long duration = duration_cast<milliseconds>(end - start).count();
float seconds = duration/1000.0f;
cout << seconds << "s" << endl;
}
void insertThenCheckForSuccess(){
cout << endl << "emplace and check success" << endl;
auto start = high_resolution_clock::now();
std::mt19937 gen(123);
std::uniform_real_distribution<> dis(0, 128);
unordered_map<int, int> grid;
int count = 0;
for(int i = 0; i < num_elements; i++){
float x = dis(gen);
float y = dis(gen);
float z = (cos(x*0.1) * sin(x*0.1) + 1.0) * 64.0;
int index = int(x) + int(y) * 128 + int(z) * 128 * 128;
auto it = grid.emplace(index, count);
if(it.second){
count++;
}
}
cout << "elements: " << count << endl;
cout << "load factor: " << grid.load_factor() << endl;
auto end = high_resolution_clock::now();
long long duration = duration_cast<milliseconds>(end - start).count();
float seconds = duration/1000.0f;
cout << seconds << "s" << endl;
}
int main(){
findThenInsert();
insertThenCheckForSuccess();
}
In entrambi i casi la dimensione della mappa è 82901 in seguito in modo da assumere il risultato è esattamente lo stesso.
find and emplace: 0.937s emplace then check: 1.268s
IIRC, 'emplace' per' unordered_map' deve essere assegnato indipendentemente dal fatto che la chiave sia presente, quindi nel secondo caso si stanno pagando per alcune allocazioni extra. –
@TheParamagneticCroissant Quel [è C++ 14] (http://en.cppreference.com/w/cpp/language/integer_literal): "Le virgolette singole opzionali (') possono essere inserite tra le cifre come separatore, sono ignorato dal compilatore. " –
@ChrisDrew oh, è abbastanza bello, non lo sapevamo. –