Sto scrivendo un programma che esegue alcuni lunghi calcoli, che posso suddividere in tutte le attività che voglio. Per ragioni di discussione, supponiamo che sto scrivendo un algoritmo per scoprire se un numero p è primo cercando di dividerlo per tutti i numeri compresi tra 2 e p-1. Questo compito può ovviamente essere suddiviso in più thread.Quando si esegue un calcolo - quanti thread devo aprire?
In realtà ho scritto un'app di esempio che fa proprio questo. Come parametro, fornisco il numero che voglio controllare, e il numero di thread da usare (ogni thread ha un intervallo di dimensioni uguali di numeri per provare a dividere p di - insieme coprono l'intero intervallo).
La mia macchina ha 8 core. Ho iniziato a eseguire il programma con un numero elevato che so essere primo (2971215073) e con 1, 2, 3 thread ecc. Fino a raggiungere 8 thread - ogni volta che il programma veniva eseguito più velocemente del precedente, che era quello che mi aspettavo. Tuttavia, quando ho provato i numeri più grandi di 8, il tempo di calcolo continuava a ridursi (anche se di poco)!
Non c'è I/O o qualcosa di simile nei miei thread, solo calcoli di CPU pura. Mi aspettavo che i tempi di esecuzione peggiorassero quando ho passato 8 thread in quanto ci sarebbe stato più cambio di contesto e il numero di thread paralleli continuava a 8. È difficile dire dove sia il picco in quanto le differenze sono molto piccole e cambiare da una corsa all'altra, tuttavia è chiaro che 50 thread in qualche modo gira più veloce di 8 (di ~ 300 ms) ...
La mia ipotesi è che poiché ho così tanti thread, ottengo più tempo di esecuzione da quando avere una porzione maggiore nel pool di thread del sistema, quindi i miei thread vengono selezionati di più. Tuttavia, non sembra logico che più thread creo, più velocemente il programma viene eseguito (altrimenti perché non tutti creano 1000 thread ??).
Qualcuno può offrire una spiegazione e forse una best practice sul numero di thread da creare rispetto al numero di core sulla macchina?
Grazie.
Il mio codice per chi è interessato (compilato su Windows, VS2012):
#include <Windows.h>
#include <conio.h>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
typedef struct
{
unsigned int primeCandidate;
unsigned int rangeStart;
unsigned int rangeEnd;
} param_t;
DWORD WINAPI isDivisible(LPVOID p)
{
param_t* param = reinterpret_cast<param_t*>(p);
for (unsigned int d = param->rangeStart; d < param->rangeEnd; ++d)
{
if (param->primeCandidate % d == 0)
{
cout << param->primeCandidate << " is divisible by " << d << endl;
return 1;
}
}
return 0;
}
bool isPrime(unsigned int primeCandidate, unsigned int numOfCores)
{
vector<HANDLE> handles(numOfCores);
vector<param_t> params(numOfCores);
for (unsigned int i = 0; i < numOfCores; ++i)
{
params[i].primeCandidate = primeCandidate;
params[i].rangeStart = (primeCandidate - 2) * (static_cast<double>(i)/numOfCores) + 2;
params[i].rangeEnd = (primeCandidate - 2) * (static_cast<double>(i+1)/numOfCores) + 2;
HANDLE h = CreateThread(nullptr, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(isDivisible), ¶ms[i], 0, 0);
if (NULL == h)
{
cout << "ERROR creating thread: " << GetLastError() << endl;
throw exception();
}
handles[i] = h;
}
DWORD ret = WaitForMultipleObjects(numOfCores, &handles[0], TRUE, INFINITE);
if (ret >= WAIT_OBJECT_0 && ret <= WAIT_OBJECT_0 + numOfCores - 1)
{
for (unsigned int i = 0; i < numOfCores; ++i)
{
DWORD exitCode = -1;
if (0 == GetExitCodeThread(handles[i], &exitCode))
{
cout << "Failed to get thread's exit code: " << GetLastError() << endl;
throw exception();
}
if (1 == exitCode)
{
return false;
}
}
return true;
}
else
{
cout << "ERROR waiting on threads: " << ret << endl;
throw exception();
}
}
int main()
{
unsigned int primeCandidate = 1;
unsigned int numOfCores = 1;
cout << "Enter prime candidate: ";
cin >> primeCandidate;
cout << "Enter # of cores (0 means all): ";
cin >> numOfCores;
while (primeCandidate > 0)
{
if (0 == numOfCores) numOfCores = thread::hardware_concurrency();
DWORD start = GetTickCount();
bool res = isPrime(primeCandidate, numOfCores);
DWORD end = GetTickCount();
cout << "Time: " << end-start << endl;
cout << primeCandidate << " is " << (res ? "" : "not ") << "prime!" << endl;
cout << "Enter prime candidate: ";
cin >> primeCandidate;
cout << "Enter # of cores (0 means all): ";
cin >> numOfCores;
}
return 0;
}
Buona domanda. Potresti postare o collegare al tuo codice di test? Inoltre, suggerirei di fare un test usando std :: async per vedere come si confronta. Penso che la maggior parte del threading in futuro userà std :: async invece di gestire i thread direttamente. – David
@ E.K. per convalidare la tua ipotesi sarebbe interessante eseguire il tuo programma su un ** sistema inattivo **, perché se esegui il tuo browser, IDE e WoW contemporaneamente potrebbero esserci strani effetti collaterali come quello che descrivi;) In ogni caso davvero interessante :) +1 – Pragmateek
Come hai diviso la sequenza? da contigue renges o sovrapponendo l'intera gamma? (Voglio dire (1,2,3,4), (5,6,7,8) o (1,3,5,7), (2,4,6,8)) –