Effizienteres C # Mandelbrotzeichnen

8

Zunächst ist mir bewusst, dass diese Frage wirklich so klingt, als ob ich nicht gesucht hätte, aber ich habe viel getan.

Ich habe einen kleinen Mandelbrot-Zeichencode für C # geschrieben, es ist im Grunde eine Windows-Form mit einer PictureBox, auf der ich das Mandelbrot-Set zeichne.

Mein Problem ist, dass es ziemlich langsam ist. Ohne einen tiefen Zoom macht es einen ziemlich guten Job und das Bewegen und Zoomen ist ziemlich glatt, dauert weniger als eine Sekunde pro Zeichnung, aber sobald ich anfange, ein wenig zu zoomen und zu Orten zu kommen, die mehr Berechnungen erfordern, wird es sehr langsam / p>

Bei anderen Mandelbrot-Anwendungen funktioniert mein Computer sehr gut an Orten, die in meiner Anwendung viel langsamer arbeiten. Ich schätze daher, dass es viel gibt, was ich tun kann, um die Geschwindigkeit zu verbessern.

Ich habe die folgenden Dinge getan, um es zu optimieren:

  • Anstatt die SetPixel GetPixel-Methoden für das Bitmap-Objekt zu verwenden, habe ich die LockBits-Methode verwendet, um direkt in den Speicher zu schreiben, wodurch die Dinge viel schneller wurden.

  • Anstatt komplexe Zahlenobjekte zu verwenden (mit Klassen, die ich selbst gemacht habe, nicht die eingebauten Klassen), emulierte ich komplexe Zahlen mit 2 Variablen, re und im. Dies ermöglichte es mir, Multiplikationen zu reduzieren, da die Quadratur des Realteils und des Imaginärteils während der Berechnung einige Zeit dauert. Daher speichere ich das Quadrat in einer Variablen und verwende das Ergebnis, ohne es neu berechnen zu müssen.

  • Ich benutze 4 Threads um das Mandelbrot zu zeichnen, jeder Thread macht ein anderes Viertel des Bildes und alle arbeiten gleichzeitig. Wie ich verstanden habe, bedeutet das, dass meine CPU 4 ihrer Kerne benutzt, um das Bild zu zeichnen.

  • Ich benutze den Escape-Time-Algorithmus, der, wie ich verstanden habe, der schnellste ist?

Hier ist meine Art, wie ich zwischen den Pixeln wechsle und berechne, es ist auskommentiert, also hoffe ich es ist verständlich:

%Vor%

Was kann ich tun, um das zu verbessern? Finden Sie offensichtliche Optimierungsprobleme in meinem Code?

Im Moment gibt es zwei Möglichkeiten, ich weiß, dass ich es verbessern kann:

  1. Ich muss einen anderen Typ für Zahlen verwenden, doppelt ist mit Genauigkeit begrenzt und ich bin mir sicher, es gibt bessere, nicht integrierte alternative Typen, die schneller sind (sie multiplizieren und fügen schneller hinzu) und haben eine höhere Genauigkeit Ich brauche nur jemanden, der mir zeigt, wo ich hinschauen muss, und mir sagen, ob es wahr ist.

  2. Ich kann die Verarbeitung auf die GPU verschieben. Ich habe keine Ahnung, wie ich das machen soll (OpenGL vielleicht? DirectX? Ist das überhaupt so einfach oder muss ich eine Menge lernen?). Wenn mir jemand Links zu passenden Tutorials zu diesem Thema schicken kann oder mir generell davon erzählen würde, wäre das super.

Vielen Dank fürs Lesen und hoffen, dass Sie mir helfen können:)

    
Omer 01.07.2013, 14:07
quelle

3 Antworten

3

Wenn Sie die Verarbeitung auf die GPU verschieben möchten, können Sie aus einer Reihe von Optionen auswählen. Da Sie C # verwenden, können Sie mit XNA HLSL verwenden. RB Whitaker hat die einfachsten XNA-Tutorials, wenn Sie diese Option wählen. Eine andere Option ist OpenCL . OpenTK kommt mit einem Demo-Programm eines Julia-Fraktals. Dies wäre sehr einfach zu modifizieren, um den Mandlebrot-Satz anzuzeigen. Siehe hier Denken Sie daran, den GLSL-Shader zu finden, der zum Quellcode gehört.

  

Über die GPU, Beispiele sind keine Hilfe für mich, weil ich absolut habe   keine Ahnung von diesem Thema, wie funktioniert es überhaupt und welche Art von   Berechnungen kann die GPU tun (oder wie wird überhaupt zugegriffen?)

Unterschiedliche GPU-Software funktioniert jedoch anders ...

Normalerweise schreibt ein Programmierer ein Programm für die GPU in eine Shader-Sprache wie HLSL, GLSL oder OpenCL. Das in C # geschriebene Programm wird den Shader-Code laden und kompilieren und dann Funktionen in einer API verwenden, um einen Job an die GPU zu senden und das Ergebnis danach zurück zu bekommen.

Sieh dir FX Composer an oder rende Affen, wenn du etwas Übung mit Shadern machen willst, ohne dich um APIs kümmern zu müssen .

Wenn Sie HLSL verwenden, sieht die Rendering-Pipeline so aus.

Der Vertex-Shader ist dafür verantwortlich, Punkte im 3D-Raum aufzunehmen und ihre Position in Ihrem 2D-Sichtfeld zu berechnen. (Keine große Sorge für Sie, da Sie in 2D arbeiten)

Der Pixel-Shader ist verantwortlich für das Anwenden von Shader-Effekten auf die Pixel, nachdem der Vertex-Shader fertig ist.

OpenCL ist eine andere Geschichte, die auf GPU-Computing für allgemeine Zwecke ausgerichtet ist (dh nicht nur Grafiken). Es ist leistungsfähiger und kann für GPUs, DSPs und Supercomputer verwendet werden.

    
sav 13.08.2013 07:18
quelle
2

WRT-Codierung für die GPU, können Sie sich Cudafy.Net ansehen (es tut auch OpenCL, das nicht an NVidia gebunden ist), um zu verstehen, was los ist, und vielleicht sogar alles zu tun, was Sie dort brauchen. Ich habe es schnell gefunden - und meine Grafikkarte - ungeeignet für meine Bedürfnisse, aber für das Mandelbrot auf der Bühne, wo du bist, sollte es in Ordnung sein.

Kurz gesagt: Sie schreiben für die GPU mit einem Geschmack von C (normalerweise Cuda C oder OpenCL), dann drücken Sie den "Kernel" (Ihre kompilierte C-Methode) auf die GPU gefolgt von irgendwelchen Quelldaten und rufen dann diesen "Kernel" auf ", oft mit Parametern zu sagen, welche Daten zu verwenden - oder vielleicht ein paar Parameter, um es zu sagen, wo die Ergebnisse in seinem Speicher platziert werden.

Wenn ich selbst fraktal gerendert habe, habe ich es aus den bereits erwähnten Gründen vermieden, zu einer Bitmap zu zeichnen, und habe die Render-Phase verschoben. Abgesehen davon tendiere ich dazu, massiv Multithreading-Code zu schreiben, was wirklich schlecht ist, um auf eine Bitmap zuzugreifen. Stattdessen schreibe ich in einen gemeinsamen Speicher - zuletzt habe ich eine MemoryMappedFile (eine eingebaute .Net-Klasse) verwendet, da dies mir eine ziemlich gute wahlfreie Zugriffsgeschwindigkeit und einen riesigen adressierbaren Bereich bietet. Ich tendiere auch dazu, meine Ergebnisse in eine Warteschlange zu schreiben und einen anderen Thread damit zu beschäftigen, die Daten an den Speicher zu übergeben; Die Rechenzeit jedes Mandelbrot-Pixels wird "zerlumpt" sein - das heißt, sie werden nicht immer die gleiche Zeitlänge haben. Infolgedessen könnte Ihr Pixel-Commit der Engpass für sehr niedrige Iterationszahlen sein. Wenn Sie es auf einen anderen Thread umstellen, bedeutet dies, dass Ihre Compute-Threads niemals auf die Fertigstellung des Speichers warten.

Ich spiele gerade mit der Buddhabrot-Visualisierung des Mandelbrot-Sets, schaue mir eine GPU an, um das Rendering zu skalieren (da es sehr lange mit der CPU dauert) und habe ein riesiges Ergebnis-Set. Ich dachte daran, ein 8-Gigapixel-Bild anzuschneiden, aber ich bin zu der Erkenntnis gekommen, dass ich aufgrund von Präzisionsproblemen von den Beschränkungen der Pixel und möglicherweise von der Gleitkommaarithmetik abweichen muss. Ich werde auch etwas neue Hardware kaufen müssen, damit ich mit der GPU anders interagieren kann - verschiedene Compute-Jobs werden zu unterschiedlichen Zeiten fertig sein (wie bei meiner vorherigen Iterationszählung), so dass ich nicht einfach Threads abfeuern und warten kann für sie alle zu vervollständigen, ohne möglicherweise viel Zeit zu verschwenden warten auf eine besonders hohe Iteration zählen Sie aus dem gesamten Batch.

Ein weiterer Punkt, den ich kaum jemals über das Mandelbrot-Set gesehen habe, ist, dass es symmetrisch ist. Sie können doppelt so viel berechnen, wie Sie benötigen.

    
user1796307 09.08.2013 13:22
quelle
1

Um die Verarbeitung auf die GPU zu verschieben, haben Sie hier viele hervorragende Beispiele:

Ссылка

Beachten Sie, dass Sie einen WebGL-fähigen Browser benötigen, um diesen Link anzuzeigen. Funktioniert am besten in Chrome.

Ich bin kein Experte für Fraktale, aber Sie scheinen mit den Optimierungen schon weit gekommen zu sein. Wenn Sie darüber hinaus gehen, kann der Code viel schwerer zu lesen und zu warten sein. Sie sollten sich also fragen, ob es sich lohnt.

Eine Technik, die ich oft in anderen Fraktalprogrammen beobachtet habe, ist folgende: Während des Zoomens berechne das Fraktal mit einer geringeren Auflösung und dehne es während des Renderns auf volle Größe. Rendern Sie dann mit voller Auflösung, sobald das Zoomen stoppt.

Ein weiterer Vorschlag ist, dass Sie bei der Verwendung mehrerer Threads darauf achten sollten, dass nicht jeder Thread Speicher anderer Threads liest / schreibt, da dies zu Cache-Kollisionen und zu Leistungseinbußen führt. Ein guter Algorithmus könnte die Arbeit in Scanlinien aufteilen (statt wie bisher vier Viertel). Erstellen Sie eine Anzahl von Threads, und weisen Sie ihnen so lange einen Scan zu, bis ein Thread verfügbar ist. Lassen Sie jeden Thread die Pixeldaten in einen lokalen Speicherbereich schreiben und kopieren Sie diesen nach jeder Zeile in die Hauptbitmap zurück (um Cache-Kollisionen zu vermeiden).

    
Ville Krumlinde 01.07.2013 17:13
quelle