Ho bisogno di riempire un valore enorme (7734500 elementi) std::vector<unsigned int>
con valori casuali e sto cercando di farlo in parallelo con più thread per ottenere una maggiore efficienza. Ecco il codice che ho finora:Riempimento di un vettore con più thread
std::random_device rd; // seed generator
std::mt19937_64 generator{rd()}; // generator initialized with seed from rd
static const unsigned int NUM_THREADS = 4;
std::uniform_int_distribution<> initialize(unsigned long long int modulus)
{
std::uniform_int_distribution<> unifDist{0, (int)(modulus-1)};
return unifDist;
}
void unifRandVectorThreadRoutine
(std::vector<unsigned int>& vector, unsigned int start,
unsigned int end, std::uniform_int_distribution<>& dist)
{
for(unsigned int i = start ; i < end ; ++i)
{
vector[i] = dist(generator);
}
}
std::vector<unsigned int> uniformRandomVector
(unsigned int rows, unsigned int columns, unsigned long long int modulus)
{
std::uniform_int_distribution<> dist = initialize(modulus);
std::thread threads[NUM_THREADS];
std::vector<unsigned int> v;
v.resize(rows*columns);
// number of entries each thread will take care of
unsigned int positionsEachThread = rows*columns/NUM_THREADS;
// all but the last thread
for(unsigned int i = 0 ; i < NUM_THREADS - 1 ; ++i)
{
threads[i] = std::thread(unifRandVectorThreadRoutine, v, i*positionsEachThread,
(i+1)*positionsEachThread, dist);
// threads[i].join();
}
// last thread
threads[NUM_THREADS - 1] = std::thread(unifRandVectorThreadRoutine, v,
(NUM_THREADS-1)*positionsEachThread, rows*columns, dist);
// threads[NUM_THREADS - 1].join();
for(unsigned int i = 0 ; i < NUM_THREADS ; ++i)
{
threads[i].join();
}
return v;
}
Al momento, ci vogliono circa 0,3 secondi: pensi che ci sia un modo per renderlo più efficiente?
Edit: Dare Ogni thread proprio generatore
Ho modificato la routine come segue
void unifRandVectorThreadRoutine
(std::vector<unsigned int>& vector, unsigned int start,
unsigned int end, std::uniform_int_distribution<>& dist)
{
std::mt19937_64 generator{rd()};
for(unsigned int i = start ; i < end ; ++i)
{
vector[i] = dist(generator);
}
}
e il tempo di esecuzione è sceso della metà. Quindi sto ancora condividendo il std::random_device
ma ogni thread ha il suo std::mt19937_64
.
Edit: Dando ogni thread proprio vettore e quindi concatenando
ho cambiato il codice come segue:
void unifRandVectorThreadRoutine
(std::vector<unsigned int>& vector, unsigned int length,
std::uniform_int_distribution<>& dist)
{
vector.reserve(length);
std::mt19937_64 generator{rd()};
for(unsigned int i = 0 ; i < length ; ++i)
{
vector.push_back(dist(generator));
}
}
e
std::vector<unsigned int> uniformRandomVector
(unsigned int rows, unsigned int columns, unsigned long long int modulus)
{
std::uniform_int_distribution<> dist = initialize(modulus);
std::thread threads[NUM_THREADS];
std::vector<unsigned int> v[NUM_THREADS];
unsigned int positionsEachThread = rows*columns/NUM_THREADS;
// all but the last thread
for(unsigned int i = 0 ; i < NUM_THREADS - 1 ; ++i)
{
threads[i] = std::thread(unifRandVectorThreadRoutine, std::ref(v[i]), positionsEachThread, dist);
}
// last thread
threads[NUM_THREADS - 1] = std::thread(unifRandVectorThreadRoutine, std::ref(v[NUM_THREADS-1]),
rows*columns - (NUM_THREADS-1)*positionsEachThread, dist);
for(unsigned int i = 0 ; i < NUM_THREADS ; ++i)
{
threads[i].join();
}
std::vector<unsigned int> finalVector;
finalVector.reserve(rows*columns);
for(unsigned int i = 0 ; i < NUM_THREADS ; ++i)
{
finalVector.insert(finalVector.end(), v[i].begin(), v[i].end());
}
return finalVector;
}
Il tempo di esecuzione è un po 'peggio di prima, quando stavo usando un solo vettore condiviso tra tutti i thread. Mi manca qualcosa o può succedere?
Edit: utilizzando un PRNG diversa + benchmark
Utilizzando un diverso PRNG (come suggerito in alcuni commenti/risposte) aiuta molto: ho provato con il xorshift+
e qui è l'implementazione Sono utilizzando:
class xorShift128PlusGenerator
{
public:
xorShift128PlusGenerator()
{
state[0] = rd();
state[1] = rd();
};
unsigned long int next()
{
unsigned long int x = state[0];
unsigned long int const y = state[1];
state[0] = y;
x ^= x << 23; // a
state[1] = x^y^(x >> 17)^(y >> 26); // b, c
return state[1] + y;
}
private:
std::random_device rd; // seed generator
unsigned long int state[2];
};
l'esame di routine è la seguente
void unifRandVectorThreadRoutine
(std::vector<unsigned int>& vector, unsigned int start,
unsigned int end)
{
xorShift128PlusGenerator prng;
for(unsigned int i = start ; i < end ; ++i)
{
vector[i] = prng.next();
}
}
Dato che ora sono a casa e sto usando una macchina diversa (e più potente), ho rifatto i test per confrontare i risultati. Ecco cosa ottengo:
- Mersenne Twister con un generatore per thread: 0,075 secondi
- xorshift128 + condivisa tra tutte le discussioni: 0.023 secondi
- xorshift128 + con un generatore per thread: 0.023 secondi
Nota: il tempo di esecuzione varia ad ogni ripetizione. Questi sono solo valori tipici.
Quindi non sembra esserci differenza se il generatore di xorshift è condiviso o meno, ma con tutti questi miglioramenti il tempo di esecuzione è diminuito in modo significativo.
Perchè unisci il thread non appena lo crei? Questo è essenzialmente lo stesso del farlo in sequenza. – TartanLlama
@TartanLlama Hai ragione! Ho cambiato il codice, ma il tempo di esecuzione è sempre lo stesso (se non leggermente peggio). Questo mi fa pensare che non sto ottenendo nulla dall'utilizzo di più thread – minomic
I tuoi thread sono in esecuzione simultaneamente (cioè core separati)? –