Laden...

6-Kern-CPU wird auch mit mehreren Threads nicht ausgelastet

Erstellt von typeof(Human) vor 11 Jahren Letzter Beitrag vor 11 Jahren 4.520 Views
T
typeof(Human) Themenstarter:in
5 Beiträge seit 2013
vor 11 Jahren
6-Kern-CPU wird auch mit mehreren Threads nicht ausgelastet

Hallo liebe Teilnehmer,

ich programmiere schon seit einiger Zeit immer wieder mal mit C# und stoße gerade auf ein Problem, zu welchem ich keine Lösung weiß:

Ich habe ein Programm geschrieben, welches Daten, die sich im Arbeitsspeicher befinden, analysiert und dann ein Ergebnisprotokoll auswirft.

Da es sich um eine recht große Datenmenge handelt (ca. 6 Mio. Objekte die über 22 Mio mal neu berechnet werden), hatte ich nun den PC gewechselt und gehofft, daß es auf meinem 6-Kern-Rechner schneller läuft.
Leider Zeigt der Task-Manager immer eine Auslastung von 10% an, wobei die Last nahezu gleichmäßig auf die Kerne verteilt wird.

Daraufhin habe ich mir gedacht, die Berechnung in 12 Threads aufzuteilen, damit ich einen Geschwindigkeitsvorteil habe. Leider war dem nicht so. Die Prozessorauslastung blieb bei exakt 10%, wobei sich die gefühlte Rechengeschwindigkeit minimiert hat.

Was ich zudem noch versucht habe:

  • bei allen Threads die Thread.Proirity auf AboveNormal -> kein Unterschied
  • Test-Thread mit Dauerschleife ins Programm hinzugefügt -> Last steigt von 10 auf lediglich 16%

Es sieht für mich so aus, als würde ganz generell meinem C#-Programm nur eine begrenzte Menge an Prozessorzeit zur Verfügung gestellt.
Kann ich das irgendwo beeinflussen?

Andere Vermutung:
Die Berechnungen haben sehr viel mit DateTime-Werten zu tun. Es werden also sehr viele DateTime-Objekte erzeugt. Kann es sein, daß dadurch meine Threads ausgebremst werden? Ansonsten finden nur recht einfache Berechnungen und Vergleiche von Zahlen statt.

Festplattenzugriffe kann ich ausschließen, da ich alle Daten vor dem Start der Berechnung in den Arbeitsspeicher lade. (List-Objekte).

Vorab vielen Dank!

Der Fehler liegt meistens zwischen den Ohren...

5.658 Beiträge seit 2006
vor 11 Jahren

Hi typeof(Human),

ohne Code kann man natürlich nur raten. Aber hast du es mal der Einfachheit halber mit Parallel.For oder Parallel.ForEach probiert?

Christian

Weeks of programming can save you hours of planning

F
10.010 Beiträge seit 2004
vor 11 Jahren

Wie und Wo erzeugst du denn die Threads?
Denn 16% ist ja 100/6, also wird nur ein Kern benutzt.

T
typeof(Human) Themenstarter:in
5 Beiträge seit 2013
vor 11 Jahren

Hallo MrSparkle,

vielen Dank für Deinen Tip!

Leider würde es jetzt den Rahmen sprengen, wenn ich meine kompletten Klassen und mathematischen Berechnungen, sowie das Threading hier als Quellcode einstellen würde. Daher habe ich versucht, das Problem möglichst genau zu beschreiben.

Prallel.For habe ich tatsächlich noch nicht ausprobiert, wobei ich auch sagen muß, daß ich noch keine Vorstellung davon habe, wie sich diese Art von Schleife auf meinen Fall anwenden ließe. Da muß ich wohl mal noch eine Nacht drüber schlafen... 😉

Da aber auch der Test-Thread, der in einer Endlosschleife zu 100% Zeit schluckt, nur eine zusätzliche Auslastung von ca. 6 % bewirkt, glaube ich an eine andere Problematik.

Es gibt hier wahrscheinlich ein ganz generelles Problem mit der Zuweisung von Prozessorkapazitäten.

Evtl. eine globale Einstellung, die mir z.Zt. nicht bekannt ist?!

@FZelle:

Die Threads werden so erzeugt:


private void ParameterLoop()
        {
            foreach (RParam p in _paramList)
            {
                _ro.param = p;
                _ro.parameterSets = _paramList.Count;
                _ro.currentParameterRun++;

                SaveParameterLog();

                for (int i = 0; i < 11; i++)
                {

                    BestThread.ThreadParam data = new BestThread.ThreadParam();
                    data.ed = _currentExchange;
                    data.from = new DateTime(_from.Year, i + 1, 1);
                    data.to = new DateTime(_to.Year, i + 1, 1).AddMonths(1).AddDays(-1);
                    data.param = p;
                    data.h = _h;
                    data.sql = _sql;

                    lock (_lockResult)
                    {
                        Thread t = new Thread(new ParameterizedThreadStart(T_NewBestThread));

                        t.IsBackground = false;
                        t.Priority = ThreadPriority.Highest;
                        t.Start(data);

                        _threadCount++;
                    }
                }

                if (StaticConfig.closeRequest)
                    return;

                while (_threadCount > 0 && _threads.Count > 0)
                {
                    Thread.Sleep(10);
                    Application.DoEvents();
                }
            }
        }

        private void T_NewBestThread(object data)
        {
            BestThread bt = new BestThread(data);

            bt.EThreadresult += new BestThread.DThreadResult(ThreadResultCallback);

            _threads.Add(bt);

            bt.Start();
            
        }

Der Taskmanager zeigt eine gleichmäßige Verteilung der Last an.

Vielen Dank für Euer Interesse! 😃

Ich bin jetzt für ca. 1 Std weg und melde mich dann wieder.

VG

Der Fehler liegt meistens zwischen den Ohren...

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo typeof(Human),

Es sieht für mich so aus, als würde ganz generell meinem C#-Programm nur eine begrenzte Menge an Prozessorzeit zur Verfügung gestellt.

das ist Unsinn.

while (...) { Thread.Sleep(10); Application.DoEvents();]  

Auch nicht so toll und vollkommen unnötig, siehe z.B. Consolenanwendung mit Timer optimieren [Semaphore/Ampel].

herbivore

T
typeof(Human) Themenstarter:in
5 Beiträge seit 2013
vor 11 Jahren

Hallo Herbivore,

vielen Dank für Deinen Vorschlag, aber vom Effekt her das Gleiche.

Mit "Das ist Unsinn" kann ich jetzt leider auch nicht so viel anfangen.

Hast Du vielleicht einen Lösungsvorschlag für das eigentliche Problem?

Vielleicht kann ich irgendwo weitere Einstellungen zum Threading vornehmen?

Vielen Dank!

Der Fehler liegt meistens zwischen den Ohren...

5.658 Beiträge seit 2006
vor 11 Jahren

Mit "Das ist Unsinn" kann ich jetzt leider auch nicht so viel anfangen.

Ist doch eine eindeutig. Jedenfalls ist deine Vermutung, daß C#-Programmen nur wenig Prozessorleistung zur Verfügung gestellt wird, ziemlich absurd.

Zum Thema Threading (Erzeugen von Threads, Threadpool, Synchronisation) gibt es im Forum zahlreiche Beiträge. Die Tips, die dort gegeben werden, solltest du erstmal umsetzen.

Prallel.For habe ich tatsächlich noch nicht ausprobiert, wobei ich auch sagen muß, daß ich noch keine Vorstellung davon habe

Schau einfach in die Doku, dort gibt es Beispiele dafür: :rtfm:

Christian

Weeks of programming can save you hours of planning

T
typeof(Human) Themenstarter:in
5 Beiträge seit 2013
vor 11 Jahren

Hallo MrSparkle,

nicht böse sein, aber ich glaube, wir reden leider aneinander vorbei.

Meine Threads werden korrekt erzeugt, sie laufen, sind synchronisiert und haben keine Deadlocks. 😉

Threads sind für mich nichts Neues. Was neu ist, ist, daß sie den Prozessor kaum in Anspruch nhemen. Dieses Problem hatte ich bisher noch nicht und im Internet (und hier) konnte ich dazu leider auch nichts finden.

Ich glaube auch nicht, daß ganz allgemein C#-Programmen nur wenig Prozessorlast zugesprochen wird. Das wäre in der Tat Unsinn. Daher habe ich ja auch geschrieben "... meinem Programm ..." Vielmehr denke ich da an mögliche Einstellungen in der Entwicklungsumgebung, wie z.B. das Anpassen auf x64 Systemen, Build-Einstellungen etc.

Eventuell gibt es auch Zusammenhänge zwichen Haupt- und Workerthreads, die noch optimiert werden könnten? Ob ich nun einen Thread aus dem Pool nehme, oder ihn anderweitig erzeuge, macht von der Ausführungsgeschwindigkeit her keinen Unterschied. (Laut Microsoft 😉 )

Alleine die Feststellung, daß ein absichtlich eingebauter Prozessorleistung fressender Thread nur 6 % der Prozessorlast verschlingt, läßt doch schon vermuten, daß das Problem nicht im Thread, bzw. der Programmierung an sich liegt. Der Thread ist ja da und läuft.
Aber er schleicht leider auf eine Weise, als ob irgendwo ein Thread.Sleep(1); eingebaut wäre, was leider nicht der Fall ist.
Und damit meine ich nicht den Thread.Sleep(10), der in der Schleife steht, der die Threads erzeugt... 😉

Hat noch jemand Ideen?

Der Fehler liegt meistens zwischen den Ohren...

16.834 Beiträge seit 2008
vor 11 Jahren

Das ist wirklich relativer Unsinn was Du schreibst.

Alleine ein


namespace ConsoleApplication1
{
    class Program
    {
        static void Main( string[ ] args )
        {
            Parallel.ForEach( Enumerable.Range( 0, 1000000000 ), i =>
            {
                var calc = i + i + i + i + i + i + i + i;
            } );

            Console.WriteLine( "Done" );
        }
    }
}



wird Deine CPU mit relativer Wahrscheinlichkeit ganz schön fordern.
Meine CPU bzw. alle 16 Kerne rennen auf 100% Volllast bei dieser Ausführung; obwohl der relevante Code nur ein mehr als simpler Dreizeiler ist.

Allein wie Deine Anwendung jedoch aufgebaut ist behaupte ich, dass Du keine Ahnung hast, was Du da eigentlich tust.
Und natürlich macht es einen Unterschied ob Du manuell einen Thread erstellst oder den ThreadPool verwendest oder ganz auf die TPL mit einem eigenen Scheduler setzt. Das sind Himmelweite Unterschiede - allein im Overhead.

Du solltest Dich dringend mit dem Thema Threading beschäftigen, bevor Du weiter mit falschen Vorstellungen rumwerkelst.
[Artikel] Multi-Threaded Programmierung

T
typeof(Human) Themenstarter:in
5 Beiträge seit 2013
vor 11 Jahren

Hallo Abt,

vielen Dank für Deine Antwort.

Habe Deinen Vorschlag zum testen eingebaut - und siehe da - 15% Prozessorauslastung. 😦
Das Problem liegt also leider woanders. Ich werde mich weiter auf die Suche begeben.

Vielen Dank an alle, die sich die Mühe gemacht haben, eine Lösung zu unterbreiten.

VG

typeof(Human)

Der Fehler liegt meistens zwischen den Ohren...

F
10.010 Beiträge seit 2004
vor 11 Jahren
data.sql = _sql;

Wenn ich soetwas lese, machst du in dem Thread evtl auch DB Zugriffe?

W
872 Beiträge seit 2005
vor 11 Jahren

Wenn Du eine hoehere CPU Auslastung sehen willst, ohne dass das Programm schneller laufen will, dann musst Du nur den ServerMode fuer den Garbage Collector einstellen.
Du musst mit einem Tool wie dem Process Explorer genauer Deine Applikation untersuchen, um ein Gefuehl fuer den Engpass zu bekommen. Die CPU ist es wohl nicht.

5.658 Beiträge seit 2006
vor 11 Jahren

Habe Deinen Vorschlag zum testen eingebaut - und siehe da - 15% Prozessorauslastung. 😦

Verstehe ich das richtig, daß das kurze Testprogramm von Abt bei dir lediglich 15% Auslastung produziert?

Weeks of programming can save you hours of planning

W
872 Beiträge seit 2005
vor 11 Jahren

Abt's Programm habe ich nicht ausprobiert, aber es kam mir schon so komisch vor, da calc nirgends benutzt wird - wenn dann sollte man calc auf der console ausgeben oder das Ergebnis sonstwo verwenden - ansonsten wird das weg optimiert.

16.834 Beiträge seit 2008
vor 11 Jahren

Nein, da wird im Debug Modus absolut nichts wegoptimiert. 😜
Das ist mit Absicht so. Es geht auch eher um die Menge von Iterationen in Kombination mit kleineren Sachen.
Es hätten sicher auch nur zwei Additionen (oder evtl gar keine) gereicht, aber ich hab einfach via Copy & Paste ein paar eingesetzt....

Und nein, man soll das garantiert **nicht **auf der Console ausgeben, da sich das x-fach negativ auf die Ausführgeschwindigkeit auswirken würde!

W
872 Beiträge seit 2005
vor 11 Jahren

Jeden Wert natuerlich nicht auf der Console - aber auch nicht im Debug Modus Performance testen.

16.834 Beiträge seit 2008
vor 11 Jahren

Finde die Aussage von Dir nicht gut.
Du kannst absolut problemlos mein Snippet im Debug laufen lassen. Das zeigt Dir, dass es auf alle Fälle möglich ist alles zu nutzen, was Dir Dein Rechner zur Verfügung stellt - und darauf kommts in diesem Falle an.

Es geht hier also nicht um die Performance, sondern es geht hier um die Tatsache, dass es schlicht und einfach funktioniert.
Dass Anwendungen im Debug Modus kein Performance-Punkt sein soll steht außer frage.

Was hier keiner weiß und der Themenersteller offensichtlich nicht zeigen will, ist was er überhaupt aus Code-Sicht tut.
Er redet viel um den heißen Brei und beschwert sich, aber mit offenen Karten spielt er leider auch nicht.
Von daher ist jegliche Diskussion zum aktuellen Zeitpunkt einfach nur sinnloses Zeitverbrennen, da Glaskugelraten.

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo Abt, hallo weimat,

aber auch nicht im Debug Modus Performance testen.

Finde die Aussage von Dir nicht gut.

Wie so oft, liegt die Wahrheit in der Mitte.

Grundsätzlich ist es natürlich richtig, dass man Performance-Test nicht in Debug-Modus durchführen sollte, zumindest dann nicht, wenn man Aussagen über das Verhalten der fertigen Anwendung treffen möchte. In diesem grundsätzlichen Sinne ist die Aussage von dir, weimat, schon wahr.

Jedoch ist das Programm von Abt überhaupt kein Performance-Test, sondern ein Test, ob es möglich ist, 100% CPU-Auslastung zu erreichen. Das kann man im Debug-Modus genauso gut, wenn nicht sogar besser. Insofern ist die Aussage in diesem Kontext unangebracht bzw. deplatziert.

herbivore

156 Beiträge seit 2010
vor 11 Jahren

Mo in,

Application.DoEvents ist eine Behelfskrücke für Leute die sich nicht mit Threads auseinander setzen wollen. Nutze doch bitte Thread::Join() wenn Du auf das Ende der Threads warten willst.

Du startest einen Thread um einen Thread zu starten. Wozu??

Dann Schreibst du irgendwo das Du deine Thread synchronisierst. Da schrillen doch die Alarmglocken. Wenn du die Threads untereinander synchronisierst ist jeder Thread sinnlos. Prüfe doch bitte ob sich Dein Problem überhaupt parallelisieren lässt. Um wenn ja wie. Wenn es sich nicht parallelisieren lässt, dann kannst du noch so viele Kerne und Threads Haben, es wird aber nicht schneller.

Hand, mogel

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo mogel,

Application.DoEvents ist eine Behelfskrücke für Leute die sich nicht mit Threads auseinander setzen wollen,

richtig, das hatte ich oben schon geschrieben.

Du startest einen Thread um einen Thread zu starten.

Was für eine Klasse BestThread ist, wissen wir nicht. Vermutlich enthält sie die Verarbeitungslogik. Es wird also wohl nicht ein Thread aus einem Thread heraus gestartet, sondern die Verarbeitungslogik aus einem Thread heraus. Das ist das gewünschte. Über den Klassennamen kann man natürlich streiten.

Dann Schreibst du irgendwo das Du deine Thread synchronisierst. Da schrillen doch die Alarmglocken.

Bei mir würden die Alarmglocken schrillen, wenn jemand schreiben würde, dass er seine Threads nicht synchronisiert. Synchronisieren bedeutet ja in diesem Kontext nicht "hintereinander ausführen", sondern "thread-safe machen". Ich denke also, du hast typeof(Human) falsch verstanden.

herbivore

156 Beiträge seit 2010
vor 11 Jahren

Was für eine Klasse BestThread ist, wissen wir nicht. Vermutlich enthält sie die Verarbeitungslogik. Es wird also wohl nicht ein Thread aus einem Thread heraus gestartet, sondern die Verarbeitungslogik aus einem Thread heraus. Das ist das gewünschte. Über den Klassennamen kann man natürlich streiten.

es hat etwas gedauert, aber Du könntest recht haben. Der Klassenname und der Methodenname lies mich in der Tat annehmen hier wird von Thread geerbt. Sollte das der Fall sein, werden 2 Threads gestartet. Ansonsten nur einer.

Bei mir würden die Alarmglocken schrillen, wenn jemand schreiben würde, dass er seine Threads nicht synchronisiert. Synchronisieren bedeutet ja in diesem Kontext nicht "hintereinander ausführen", sondern "thread-safe machen".

doch - "thread-safe" kann aber dummerweise auch zu einem "hintereinander ausführen" führen. ThreadA hat BM, ThreadB wartet. ThreadA gibt BM frei, ThreadB fängt an und lockt BM, ThreadA wartet sofort wieder auf BM. Genau deshalb die "Alarmglocken". Genau deshalb erstmal prüfen ob sich das Problem überhaupt parallelisieren lässt.

hand, mogel

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo mogel,

Der Klassenname und der Methodenname lies mich in der Tat annehmen hier wird von Thread geerbt.

die Klasse Thread ist sealed. BestThread kann also gar nicht von Thread geerbt haben. (Möglicherweise hast du an Java gedacht, da kann man von der Thread-Klasse erben).

doch - "thread-safe" kann aber dummerweise auch zu einem "hintereinander ausführen" führen.

Kann führen, muss aber nicht. Sicher, wenn Synchronisation nie dazu führen würde, dass bestimmte Anwendungen hintereinander bzw. zumindest nicht gleichzeitig ausgeführt werden, bräuchte man sie vielleicht nicht. Anderseits ist Synchronisation von Threads der Normalfall und kein Grund für Alarmglocken. Natürlich kann und sollte man prüfen, ob die Synchronisierung Ursache für (die) Verzögerungen ist. Aber ganz ruhig und unaufgeregt, ohne sich von irgendwelchen Alarmglocken irritieren zu lassen. 😃

Vielleicht hebst du auch auf das Amdahlsches Gesetz ab. Je größer der Anteil von nicht parallelisierbaren Aufgaben, desto geringer die mögliche Beschleunigung durch Parallelisierung.

Das sollte man natürlich auch im Blick behalten, aber bei typeof(Human) scheint es ja gar nicht an der konkreten Aufgabe bzw. an der konkreten Implementierung zu liegen, denn das unzweifelhaft vollständig parallelisierbare / vollständig parallelisierte Testprogramm von Abt zeigt das gleiche Problem.

herbivore