Ich schreibe ein Programm, das einige lange Berechnungen durchführt, in die ich so viele Aufgaben einbinden kann, wie ich möchte. Für die Diskussion nehmen wir an, dass ich einen Algorithmus schreibe, um herauszufinden, ob eine Zahl p eine Primzahl ist oder nicht, indem ich versuche, sie durch alle Zahlen zwischen 2 und p-1 zu teilen. Diese Aufgabe kann offensichtlich auf viele Threads aufgeteilt werden.Bei einer Berechnung - wie viele Threads soll ich öffnen?
Ich schrieb tatsächlich eine Beispiel-App, die genau das tut. Als Parameter gebe ich die Nummer an, nach der ich suchen möchte, und die Anzahl der zu verwendenden Threads (jedem Thread wird ein Bereich von gleicher Größe gegeben, um p zu teilen und zu teilen - zusammen decken sie den gesamten Bereich ab).
Meine Maschine hat 8 Kerne. Ich fing an, das Programm mit einer großen Zahl, die ich weiß, ist Prime (2971215073), und mit 1, 2, 3 Threads usw., bis 8 Threads zu erreichen - jedes Mal lief das Programm schneller als das vorherige, was ich erwartet hatte. Wenn ich jedoch Zahlen größer als 8 ausprobierte, wurde die Rechenzeit tatsächlich immer kleiner (wenn auch nur geringfügig)!
Es gibt keine I/O oder ähnliches in meinen Threads, nur reine CPU-Berechnungen. Ich hatte erwartet, dass die Laufzeit sich verschlechterte, wenn ich 8 Threads passierte, da es mehr Kontextwechsel geben würde und die Anzahl der parallel laufenden Threads bei 8 bleibt. Es ist schwer zu sagen, wo der Peak ist, da die Unterschiede sehr gering sind und sich ändern von einem Lauf zum anderen, aber es ist klar, dass zB 50 Threads irgendwie schneller als 8 läuft (um ~ 300 ms) ...
Meine Vermutung ist, dass, da ich so viele Threads habe, ich mehr Laufzeit seit ich bekomme einen größeren Anteil im Thread-Pool des Systems haben, sodass meine Threads mehr ausgewählt werden. Es scheint jedoch keinen Sinn zu machen, dass je mehr Threads ich erstelle, desto schneller läuft das Programm (sonst erstellen nicht alle 1000 Threads ??).
Kann jemand eine Erklärung geben und vielleicht eine Best-Practice, wie viele Threads relativ zur Anzahl der Kerne auf der Maschine zu erstellen sind?
Danke.
Mein Code für wer interessiert (kompiliert unter 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;
}
Gute Frage. Können Sie Ihren Testcode veröffentlichen oder verlinken? Außerdem würde ich vorschlagen, einen Test mit std :: async zu machen, um zu sehen, wie es vergleicht. Ich denke, die Mehrheit der Threads in der Zukunft wird std :: async verwenden, anstatt Threads direkt zu verwalten. – David
@ E.K. um deine Hypothese zu überprüfen, wäre es interessant, dein Programm auf einem ** idle system ** laufen zu lassen, denn wenn du deinen Browser, IDE und WoW gleichzeitig ausführst, könnte es seltsame Nebeneffekte geben wie die, die du beschreibst;) Wie auch immer wirklich interessant :) +1 – Pragmateek
Wie hast du die Sequenz geteilt? durch zusammenhängende renges oder durch Überlappung der gesamten Reichweite? (Ich meine (1,2,3,4), (5,6,7,8) oder (1,3,5,7), (2,4,6,8)) –