Laden...

Parallel.For - Core-Auslastung liegt nur bei 60% !

Erstellt von tonka vor 13 Jahren Letzter Beitrag vor 13 Jahren 1.758 Views
tonka Themenstarter:in
373 Beiträge seit 2006
vor 13 Jahren
Parallel.For - Core-Auslastung liegt nur bei 60% !

Hy@everybody,

ich arbeite schon seit ca. einem Jahr an einem RayTracer (kein 0815 Tracer für Spiele, sonder ein physikalisch absolut korrekter Tracer).

Ich habe zwei Konfigurationseinstellungen für die Assemblies. Einmal "SingleCore" und einmal "MultiCore". Die Funktionen (und deren Geschwindigkeit) des Tracers werden im SingleCore - Modus geschrieben/geprüft. Im MultiCore versuche ich diese zu Parallelisieren.

Mein Problem ist folgendes: Ich schaffe es nicht die einzelnen Kerne zu 100% zu belasten (dass das nicht exakt möglich ist ist mir schon klar 😃 ). Jeder Kern (auf meiner Maschine 4 Kerne) werden nur zu ca. 60% ausgelastet. Nun habe ich den Effekt, das meine SingleCore-Variante fast doppelt so schnell ist wie die MultiCore-Variante und für mich ist das ein großes Rätsel. Ich habe schon einige Sachen ausprobiert, es hat aber leider nicht zu einem wirklichen Erfolg geführt.

Meine erste Vermutung war, das die Threads zu schlecht ausgelastet sind, somit habe ich einem Thread mehrere Strahlen gegeben => Änderung 0

Ich habe die schreibenden Zugriffe (die Sensoren) auskommentiert => Änderung 0

Für mich ist das echt ein Rätsel. Hier eine kurze Erklärung meines Programms:
Aus einem sehr bekannten CAD-System werden Geometrieobjekte in Dreiecke konvertiert (gemesht) und in eine XML-Datei geschrieben. Diese Datei wird dann von meinem Tracer eingelesen und in einem großen Objekt (class Scene3D) gehalten. Im Scenen-Objekt ist auch ein spezieller Strahlengenerator, der aus Dateien Strahldaten einliest und dem Tracer zu Verfügung stellt. Jeder einzelne Strahl wird in die Scene geschickt und getract. Sollte ein Strahl einen Sensor treffen, so wird die logischerweise gespeichert.

So, hier nun meine zwei Code-Variante:
-) SingleCore


this.traceInfo.Multithreading = false;
                TraceInfo localInfo = this.traceInfo;// trace-information, ob z.B. manche Strahlgänge nicht berechenbar sind
                for (Int32 k = 0; k < CountOfRaysForIteration; k++)
                {                    
                    if (IsCancel)// abfrage für cancel
                    {
                        break;
                    }
                    RayCalculation ray = null;
                    this.Scene.Source.GetRay(k, ref ray);// von der Scene wird der zu berechnende Strahl ermmittelt
                    ActualRayCount += ray.RepeatTime;//wieviele rays sind bis jetzt simuliert worden

                    TraceRay(ray, RefractionIndexEnvironment, AbsorptionCoefficientEnvironment, ref localInfo,bw);//die Hauptfunktion, hier wird ein Strahl in die Scene geschickt und verfolgt (trace)
                    
                    if (ActualRayCount % RayCountEventTick == 0)//alle n Strahlen wird ein Event für den Fortschrittsbalken ausgelöst
                    {
                        OnRayCountTick(new RayCountEventArgs(ActualRayCount));
                    }
                } 

-) MultiCore


this.traceInfo.Multithreading = true;
                Object localobj = new object();


                try
                {
                    //for (Int32 k = 0; k < CountOfRaysForIteration; k++)
                    Parallel.For<TraceInfo>(0, CountOfRaysForIteration, () => new TraceInfo(), (k, loop, localstate) =>
                    {
#if DEBUG
                        FileStream fss = null;
                        
#endif
                        BinaryWriter bww = null;
                        try
                        {
                            if (IsCancel)
                            {
                                loop.Break();
                            }
#if DEBUG
                            fss = new FileStream(Scene.OutputDirectory + Scene.SimulationParameters.SimulationName + ".lef", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write);
                            bww = new BinaryWriter(fss);
#endif
                            ////
                            RayCalculation ray = null;
                            this.Scene.Source.GetRay((int)k, ref ray);
                            lock (localobj) { ActualRayCount += ray.RepeatTime; }
                            TraceRay(ray, RefractionIndexEnvironment, AbsorptionCoefficientEnvironment, ref localstate, bww);
                            if (ActualRayCount % RayCountEventTick == 0)
                            {
                                OnRayCountTick(new RayCountEventArgs(ActualRayCount));
                            }
                        }
                        catch (Exception ex)
                        {
                            throw ex;
                        }
#if DEBUG
                        finally
                        {
                            if (bww != null)
                            {
                                bww.Close();
                                bww = null;
                            }
                            if (fss != null)
                            {
                                fss.Close();
                                fss = null;
                            }
                        }
#endif

                        return localstate;
                    },
                        (localstate) => { lock (localobj) { traceInfo += localstate; } }
                    );
                }
                catch (AggregateException ex)
                {
                    String Text = "";
                    foreach (Exception e in ex.InnerExceptions)
                    {
                        Text += e.Message + "\n";
                    }
                    throw new Exception(Text);
                }

Hatte jemand schon mal ähnliche Probleme?

MfG
Tonka

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo tonka,

Gründe, warum ein Multithreading-Programm langsamer laufen kann als ein Singlethreading-Programm gibt es viele. Im einfachsten Fall wirft der aktuelle Thread immer gerade die Daten aus dem Cache, die der nächste Thread brauchen wird. Ob das bei dir der Fall ist, weiß ich nicht. Aber denk mal in Richtung solcher und ähnlicher Konflikte.

herbivore

Gelöschter Account
vor 13 Jahren

limitierener Faktor kann auch die Festplatte sein, sowie Synchronisierungs-Overhead.

Du hast z.b. die Sache mit dem Filestream drinn.. Ist zwar nur bei DEBUG variablen aktiv aber ohne diese Varaible, kompiliert dein Code nicht... Daher vermute ich mal, das du es noch nicht ohne hast laufen lassen?

Weiter wirfst du ein Event. Wenn in diesem Die UI geupdated wird, mit z.b. Invoke, dann entstehen hier unnötige Wartezeiten für jeden Thread.

6.862 Beiträge seit 2003
vor 13 Jahren

VS hat auch nen prima Tool um sowas zu analysieren:
Concurrency Visualizer

Ohne den Code genau anzuschaun würd ich spontan auf die locks tippen. Du zählst ja die Anzahl der Strahlen hoch und musst jedesmal dafür locken. Das kostet.

Baka wa shinanakya naoranai.

Mein XING Profil.

tonka Themenstarter:in
373 Beiträge seit 2006
vor 13 Jahren

Gründe, warum ein Multithreading-Programm langsamer laufen kann als ein Singlethreading-Programm gibt es viele. Im einfachsten Fall wirft der aktuelle Thread immer gerade die Daten aus dem Cache, die der nächste Thread brauchen wird. Ob das bei dir der Fall ist, weiß ich nicht. Aber denk mal in Richtung solcher und ähnlicher Konflikte.

Das es genügen Gründe gibt ist klar 😃. Wenn ich das Schreiben auf die Sensoren weglasse, so gibt es (aus meiner Sicht) keine gemeinsamen Daten, das was einer der Threads berechnet interessiert den anderen eigentlich nicht (RayTracen gilt ja als Hochparallelisierbar) oder spricht du von anderen Daten? Könnte das große Szenenobjekt (kann schon mal 1 bis 2GB groß sein) ein Problem sein, es wird von dort gelesen aber nicht geschrieben?

limitierener Faktor kann auch die Festplatte sein, sowie Synchronisierungs-Overhead.

Festplatte, es wird bei mir alles in den Arbeitsspeicher geschrieben! Meinest du den "Synchronisierungs-Overhead" der Threads?

Du hast z.b. die Sache mit dem Filestream drinn.. Ist zwar nur bei DEBUG variablen aktiv aber ohne diese Varaible, kompiliert dein Code nicht... Daher vermute ich mal, das du es noch nicht ohne hast laufen lassen?

Das verstehe ich nicht ganz, wenn die DEBUG -Konstante gesetzt ist ist der BinaryWriter immer null und wird auch nicht genutzt, da in der TraceRay-Funktion ebenfalls eine #if eingebaut ist!

Weiter wirfst du ein Event. Wenn in diesem Die UI geupdated wird, mit z.b. Invoke, dann entstehen hier unnötige Wartezeiten für jeden Thread.

Das passiert momentan nur alle 1000 abgearbeiteten Strahlen. Gibts hierfür eine bessere Lösung? Ich brauch den Fortschrittbalken, den manche Simulation laufen bis zu 3 Tage!

VS hat auch nen prima Tool um sowas zu analysieren:
Concurrency Visualizer

Danke für den Tipp, werd ich mir mal anschauen.

Gelöschter Account
vor 13 Jahren

Festplatte, es wird bei mir alles in den Arbeitsspeicher geschrieben!

-->

fss = new FileStream(Scene.OutputDirectory + Scene.SimulationParameters.SimulationName + ".lef", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write);

Versuche es auch mal in einem Performance-Profiler. Der kann die sagen, wo Wartezeiten entstehen bzw kann er einem hinweise darauf geben.

tonka Themenstarter:in
373 Beiträge seit 2006
vor 13 Jahren

Versuche es auch mal in einem Performance-Profiler. Der kann die sagen, wo Wartezeiten entstehen bzw kann er einem hinweise darauf geben.

Den probiere ich gerade aus - ich kann jedoch leider die Option "Verhalten der Multithreadanwendung visualisieren" aktiveren => "Erfordert Infrastruktur, die unter dieser Version von Windows nicht verfügbar ist". Ich habe WinxP x64, das ist vermtulich das Problem => kann man da irgendetwas nachinstallieren?

Gibts für den "Concurrency Visualizer" irgendwo eine gute Beschreibung, ich blick da momentan nicht so durch?

F
323 Beiträge seit 2007
vor 13 Jahren

Ich glaube er meinte so etwas wie den kostenlosen Profiler von Eqatec.

6.862 Beiträge seit 2003
vor 13 Jahren

Hallo,

XP ist einfach alt...
Schau mal hier. Da steht

The Concurrency Visualizer relies on Event Tracing for Windows functionality that is present in Windows Vista and later versions. Daher wirst du damit wohl nicht arbeiten können obwohls eigentlich DAS Tool ist um multithreaded Anwendungen mit VS 2010 ordentlich zu debuggen und visualisieren.

Ich glaube er meinte so etwas wie den kostenlosen Profiler von Eqatec. Ich kenn den genannten Profiler nicht, aber bei "normalen" Profilern hat man normalerweise keine Chance das multithreaded Verhalten einer Anwendung ordentlich nachzuvollziehen, da zwar Methodenaufrufe z.B. gemessen werden, inwieweit die aber parallel ausgeführt wurden, wann die Threads welchen Status hatten (z.b. auf IO zugriffe gwartet haben) usw. kriegt man nicht raus.

Baka wa shinanakya naoranai.

Mein XING Profil.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo tonka,

Wenn ich das Schreiben auf die Sensoren weglasse, so gibt es (aus meiner Sicht) keine gemeinsamen Daten, das was einer der Threads berechnet interessiert den anderen eigentlich nicht

eben drum kann es ja passieren, dass die Threads sich gegenseitig aus dem (gemeinsam benutzen L2-)Cache werfen. Würden alle die gleichen Daten benutzen, würde das ja gerade nicht passieren.

herbivore

Gelöschter Account
vor 13 Jahren

Ich kenn den genannten Profiler nicht, aber bei "normalen" Profilern hat man normalerweise keine Chance das multithreaded Verhalten einer Anwendung ordentlich nachzuvollziehen, da zwar Methodenaufrufe z.B. gemessen werden, inwieweit die aber parallel ausgeführt wurden, wann die Threads welchen Status hatten (z.b. auf IO zugriffe gwartet haben) usw. kriegt man nicht raus.

Man Sieht, bei welcher Zeile Code, wieviel Zeit verbraten wurde. Das gibt einem Hinweise.... Wenn es die Zeile mit dem Lock ist, dann weiß man schon, das der größte fresser der Synchronisierungs-Code ist....

6.862 Beiträge seit 2003
vor 13 Jahren

Okay, bei so einem Fehlerbild hast du recht. Aber es gibt ja genug andere Fehlerquellen beim Multithreading 😃

Baka wa shinanakya naoranai.

Mein XING Profil.

2.760 Beiträge seit 2006
vor 13 Jahren

Das passiert momentan nur alle 1000 abgearbeiteten Strahlen. Gibts hierfür eine bessere Lösung? Ich brauch den Fortschrittbalken, den manche Simulation laufen bis zu 3 Tage!

Dann solltest du darüber nachdenken deinen RayTracer Code in einen Service (und fürs debugging in eine Konsolenanwendung) zu packen und die GUI per Remoting etc. den aktuellen Status pollen zu lassen. Dürfte zwar nicht besonders an der Performance Schraube drehen aber würde ich trotzdem für das bessere Vorgehen halten.

tonka Themenstarter:in
373 Beiträge seit 2006
vor 13 Jahren

eben drum kann es ja passieren, dass die Threads sich gegenseitig aus dem (gemeinsam benutzen L2-)Cache werfen. Würden alle die gleichen Daten benutzen, würde das ja gerade nicht passieren.

Das verstehe ich jetzt nicht ganz. Ist das Objekt Scene3D (in dem alle Dreiecke, Strahlen, etc. gespiecher sind) das Problem, da die Threads die Funnktionen und somit die Daten des Objekts benutzten?? Wenn ja, wie kann man sonst die ganzen Daten zu Verfügung stellen.

MfG

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo tonka,

Das verstehe ich jetzt nicht ganz.

noch nie von (Cache) Thrashing gehört? Statt wie im Wikipedia Artikel beschrieben, musst du nur "Hauptspeicher" durch "Cache" und "Festplatte" durch "Hauptspeicher" ersetzen. Wie schon gesagt ist das Grundproblem, dass Threads sich möglicherweise immer gegenseitig die Daten aus dem Cache werfen.

herbivore

tonka Themenstarter:in
373 Beiträge seit 2006
vor 13 Jahren

Jetzt hab ich schon davon gehört 😄

Ok, so in der Art hab ich mir das schon gedacht, jedoch fällt mir nicht wirklich ein Lösung für das Problem ein. Muss ich das Scene3D Objket in seine Einzelteil aufspalten, oda wie kann ich diesem Problem entgegenwirken?

Gelöschter Account
vor 13 Jahren

erstmal solltest du das Problem lokalisieren...

tonka Themenstarter:in
373 Beiträge seit 2006
vor 13 Jahren

Naja, das Problem ist ja schon lokalisiert, ich habe nur ein großes Scene3D-Objekt, das sich die Threads teilen, alle anderen Variablen werden lokal in den einzelnen Threads erstellt.

Ich verstehe ja das Problem, hab nur nicht die gerinste Ahnung wie ich das anderes schreiben könnte.

Gelöschter Account
vor 13 Jahren

nein, das Problem wurde nicht lokalisiert. du hast hier mehrere mögliche Ursachen und du hast nicht eine einzige bislang ausschließen können...

Mir ist das ja prinzipiell egal aber bevor ich meinen Code umschreibe, versuche ich erstmal mit nicht invasiven Methoden die Ursache zu finden oder einzuschränken.

tonka Themenstarter:in
373 Beiträge seit 2006
vor 13 Jahren

du hast hier mehrere mögliche Ursachen und du hast nicht eine einzige bislang ausschließen können...

Welche sollten das sein, das lock'en für den Fortschrittszähler hab ich scho ausgeschlossen, das auslösen des Events habe ich ebenfalls auskommentiert und alle schreiben Zugriff sind deaktiviert => Ergebnis: Einfluss nicht Nennenswert. Das einzige was übrig bleibt ist das Scene3D Objket, auf das nur lesend zugegriffen wird. Ein tracer ist ja eigentlich sehr simpel aufgebaut

1.) Lese Strahl aus Datei ein
2.) Verschneide alle Dreiecke mit dem Strahl und gib mir das näheste Dreick zurück
3.) berechnen Brechung/Reflexion/Absorbition etc.
4.) generiere neuen Strahl
5.) widerhole Schritt 2 bis 4 bis kein Dreieck mehr getroffen wird

Die Schritt 1 bis 4 greifen lesen auf Scene3D zu, auf sonst nichts. Der Schritt 2 bis 4 wird tausendemale aufgerufen

Welche Fehlerursachen bleiben da noch über?

tonka Themenstarter:in
373 Beiträge seit 2006
vor 13 Jahren

So, hab den "Fehler" jetzt gefunden. Es ist tatsächlich der Zugriff auf das Scene3D-Objekt. Umso mehr Zugriff auf Scene3D ausgeführt werden umso langsamer wird das Programm. Das einzige was mir einfällt ist, für jeden einzelnen Kern einen Klon des Scene3D-Objekt zu erstellen (und im ThreadLocalState speichern) um keine gemeinsamen Resource zu nutzten!

Ist diese Annahme falsch?