std::thread arbeitet mit weniger Threads als vorgesehen



  • Guten Morgen, allerseits.
    Ich bin neu hier im Forum und hoffe, dass dieser Beitrag an der richtigen Stelle angesiedelt ist.

    Bevor es zum konkreten Code geht, ein mal kurz den Sachverhalt.
    Ich versuche mich derzeit daran mich mit der parallelen Datenverarbeitung auseinander zu setzen, für diesen Zweck habe ich mir die recht einfache aber rechenintensive Aufgabe gestellt, nämlich Primzahlen zu finden, die Ergebnisse lassen sich schnell prüfen und die Berechnung ist einfach durch zu führen.
    Dies funktioniert soweit auch Fehlerfrei. (Die ersten 340059 Primzahlen in 0,172s)
    Jedoch ist mir aufgefallen, dass sobald das Programm in die 5. Iteration geht nur 8 der angestrebten 12 Threads ausgelastet werden, da ich keinen Fehler finden kann, folgt nun der entsprechende Quellcode, in der Hoffnung, dass sich hier jemand findet, der dieses Verhalten erklären kann.

    Zunächst die Includes mit der Funktion die die Primzahlenberechnung durch führt.

    #include <iostream>
    #include <thread>
    #include <math.h>
    #include <cstdint>
    #include <vector>
    #include <algorithm>
    
    typedef uint_fast64_t prime_type;
    
    void prime_calc(const std::vector<prime_type> &prime_list, std::vector<prime_type> &prime_part_list, prime_type nThreads, prime_type ThreadID)
    {
    
    	prime_type prime_candidate = prime_list.back()+2+2*ThreadID;
    	prime_type last_check = 0;
    
    	bool is_prime = true;
    
    	while(UINT32_MAX > prime_candidate)
    	{
    		last_check = sqrt(prime_candidate)+1;
    		if(last_check > prime_list.back())
    			break;
    		for(prime_type i = 0; prime_list[i] < last_check; ++i)
    		{
    			if(prime_candidate%prime_list[i] == 0)
    			{
    				is_prime = false;
    				break;
    			}
    		}
    		if(is_prime)
    			prime_part_list.push_back(prime_candidate);
    		else
    			is_prime = true;
    		prime_candidate += 2*nThreads;
    	}
    }
    

    In dieser Funktion erfolgt die Berechnung so schnell wie es mir möglich ist, ohne dazu einen speziellen Algorithmus wie ein Sieb zu verwenden. Da es mir hier eher um das Verständnis der Parallelen Verarbeitung als um das tiefe Verständnis der Pirmzahlenberechnung geht. In diesem Teil vermute ich keinen Fehler, doch der Vollständigkeit halber und damit die Übergabeparameter klar sind, habe ich sie hinzu gefügt.

    Nun aber zum eigentlichen Hauptprogramm

    int main()
    {
    	std::vector<prime_type> prime_list = {2,3};
    	std::vector<std::vector<prime_type>> prime_part_list;
    	prime_type nThreads = std::thread::hardware_concurrency();
    	prime_part_list.resize(nThreads);
    
    	for(int i = 0; i < 5; i++)
    	{
    		std::vector<std::thread> threads;
    		std::vector<prime_type> new_primes;
    		for(prime_type i = 0; i < nThreads; i++)
    		{
    			threads.push_back(std::thread(prime_calc, std::ref(prime_list), std::ref(prime_part_list[i]), nThreads, i));
    		}
    		for(prime_type i = 0; i < nThreads; i++)
    		{
    			threads[i].join();
    			new_primes.insert(new_primes.end(),prime_part_list[i].begin(),prime_part_list[i].end());
    			prime_part_list[i].clear();
    		}
    		sort(new_primes.begin(), new_primes.end());
    		prime_list.insert(prime_list.end(), new_primes.begin(), new_primes.end());
    		new_primes.clear();
    		threads.clear();
    		std::cout << "Number of primes: " << prime_list.size() << std::endl;
    		std::cout << "----------------------------------------" << std::endl;
    		std::cout << "Highest prime: " << prime_list.back() << std::endl;
    		std::cout << "----------------------------------------" << std::endl;
    	}
    }
    

    Hier werden in den ersten Zeilen die benötigten Container vorbereitet und die Anzahl der zur Verfügung stehenden Threads ermittelt. std::thread::hardware_concurrency() gibt hier bei einem Ryzen 2600 richtigerweise 12 zurück.
    Anschließend erfolgt die Schleife mit deren Hilfe ich die Threads anlege und auch die Primzahlenliste erweitere sobald alle Threads mit ihren Berechnungen zu Ende sind.
    Dies scheint auch für die ersten 4 Iterationen wie vorgesehen zu funktionieren, da der Prozess knapp 1200% CPU Zeit erreicht (Bei Windows wären dies 100%) , doch nach wenigen Sekunden liegt der Prozess nur noch bei 800% CPU Zeit und ich habe ehrlich gesagt absolut keine Ahnung wieso das so ist.
    Eingesetzt wird g++ 7.4 auf einem Linux System.

    Ich hoffe, das dies alle wichtigen Informationen sind, und der Beitrag nicht viel zu lang geworden ist, damit mir hier eventuell geholfen werden kann.

    Mit freundlichen Grüßen
    Marcel



  • 12 Threads bedeuten aber nicht zwangsweise, daß diese jeweils gleichverteilt auf den 12 Prozessorkernen laufen. Es kann also sein, daß der Scheduler mehrere der Threads auf den gleichen Kern verteilt.

    Du könntest aber versuchen, die Threads zu "pinnen", s. z.B. Pinning a thread to a core in a cpuset through C sowie CPU Affinity Masks (Putting Threads on different CPUs).

    Unter Windows gibt es die Funktion GetCurrentProcessorNumber, um an die aktuelle Prozessornummer aus einem Thread heraus zu kommen (und für Linux gibt es sicherlich eine ähnliche).

    Edit: Für Linux dürfte dies getcpu bzw. sched_getcpu sein.



  • ja wenn du parallelisieren willst, musst du dem scheduler das auch mitteilen. computer sind blöd und können nicht selbstständig denken, auch wenn diverse werbeabteilungen immer was von "smart" und "ki" erzählen.



  • Vielen Dank erst mal für die Antworten, das mit dem Pinnen werde ich mir auf jeden Fall mal genauer ansehen.
    Jedoch ist mir nach genauer Betrachtung mittels "htop" aufgefallen, dass nur 8 Prozesse laufen, was die beobachtete geringe CPU Auslastung erklären würde.
    Hilft in diesem Fall das Pinning auch?
    Oder sind bei 8 Prozessen schlicht 8 Kerne in Verwendung und ende?

    EDIT:
    Fehlerkorrektur und Ergänzung.

    Zu beginn werden tatsächlich 12 Prozesse angelegt, diese laufen auch für ein paar Sekunden, bis dann nur noch 8 übrig bleiben.
    Die Frage die ich mir hierbei nun stelle ist, wieso nur?



  • Haben sich evtl. dann schon einige der Threads beendet?
    Mach doch mal eine Ausgabe am Ende der prime_calc-Funktion.



  • @Th69 sagte in std::thread arbeitet mit weniger Threads als vorgesehen:

    Haben sich evtl. dann schon einige der Threads beendet?

    Daran habe ich tatsächlich nicht gedacht, da mir die Laufzeit zu unterschiedlich war.
    Ja, die Threads sind früher fertig als die anderen, jedoch um einen enorm großen Faktor, damit habe ich ehrlich gesagt nicht gerechnet.



  • naja solange du kein echtzeit-scheduling betreibst, könnte es sein, dass sich die anderen threads auch noch die rechenzeit mit dem betriebssystem teilen müssen. keiner weiß jetzt genau ob, wie und warum das so ist.


Log in to reply