Parallele Rausch- und Zufallszahlenfunktionen für OpenCL-Kerne

Ursprünglicher Autor: THOMAS C.
  • Übersetzung
Der diesem Artikel beiliegende Noise-Beispielcode enthält eine Implementierung des Perlin-Algorithmus zur Rauschgenerierung, mit dem sich natürlich aussehende Texturen wie Marmor oder Wolken für dreidimensionale Grafiken erzeugen lassen. Der Artikel enthält einen Test, der den Perlin-Rauschalgorithmus verwendet, um ein Wolkenbild zu erstellen. (Weitere Informationen zum Rauschalgorithmus von Perlin finden Sie unter "Referenzen".) Zweidimensionale und dreidimensionale Versionen des Algorithmus sind enthalten. Dies bedeutet, dass Funktionen zwei oder drei Datensätze als Eingabe verwenden, um einen Ausgabewert des Perlin-Rauschens zu erzeugen.
Das Rauschbeispiel enthält auch RNG-Funktionen (Pseudo Random Number Generator), die relativ gute Ergebnisse liefern, die ausreichen, um das resultierende Bild wirklich zufällig aussehen zu lassen. Es sind eindimensionale, zweidimensionale und dreidimensionale Versionen enthalten: Die Anzahl der Messungen entspricht in diesem Fall der Anzahl der Sätze von Eingabedaten, auf deren Grundlage ein pseudozufälliger Ausgabewert gebildet wird.

Einführung und Motivation


In vielen Anwendungen ist ein gewisses Maß an "Zufälligkeit" oder genauer "Pseudozufälligkeit" erforderlich. Wir sprechen von Bedeutungssequenzen, die einer Person willkürlich, unberechenbar, dh „Lärm“, erscheinen. Darüber hinaus erfordern Anwendungen aus Gründen der Wiederholbarkeit häufig einen Zufallszahlengenerator, um genau dieselben Sequenzen mit ausreichender Zuverlässigkeit erzeugen zu können, wenn sie dieselben Eingabewerte (oder Wertesätze) empfangen.

In den meisten Zufallszahlengeneratoralgorithmen ist diese Anforderung aufgrund der Tatsache erfüllt, dass jeder erzeugte Wert vom zuvor erzeugten Wert abhängt und der erste erzeugte Wert in der Sequenz direkt vom Eingabewert abgeleitet wird. Dieser Ansatz für Zufallszahlengeneratoren ist für Sprachen mit einem hohen Grad an parallelem Rechnen wie OpenCL schwierig. Wenn wir jeden der vielen Rechen-Threads zwingen, auf eine sequentielle Quelle von Zufallszahlen zu warten, werden alle Vorteile der Parallelisierung und der auf Parallelverarbeitung basierenden Algorithmen auf Null reduziert.

Eine Möglichkeit, dieses Problem zu lösen, besteht darin, im Voraus eine große Tabelle mit Zufallswerten zu berechnen. Danach erstellt jeder der parallelen Threads eindeutige, aber streng definierte Indizes für diese Tabelle. Beispielsweise kann ein OpenCL- Kernel , der ein Bild verarbeitet, einen Eintrag aus einer vorab erstellten Tabelle auswählen, indem er einen Index basierend auf den Koordinaten des Pixels berechnet, das vom Kernel verarbeitet oder erstellt wird.

Der Nachteil dieses Ansatzes ist die Notwendigkeit eines langen sequentiellen Prozesses zum Erstellen von Zufallszahlen vor dem Starten des parallelen Algorithmus, wodurch die Leistungssteigerung während der Parallelisierung begrenzt wird. Auch in diesem Fall ist es im Voraus (vor dem Starten des parallelen Algorithmus) erforderlich, die Anzahl der benötigten Zufallszahlen zumindest annähernd zu kennen. Dies kann für parallele Algorithmen schwierig sein, die dynamisch bestimmen müssen, wie viele Zufallswerte von jedem Thread verwendet werden.

Die OpenCL-Funktionen auf Kernelebene im Noise-Beispielcode verfolgen einen Ansatz, der für den Algorithmus zur Aufteilung der Arbeit in parallele Operationen, die in OpenCL verwendet werden, besser geeignet ist.

Erstellen Sie Rauschen und Zufallszahlen für OpenCL


In OpenCL wird ein globaler Arbeitsbereich (ein Array von Arbeitselementen) mit einer, zwei oder drei Dimensionen definiert. Jedes Arbeitselement in diesem globalen Raum verfügt über einen eindeutigen Satz von identifizierenden ganzzahligen Werten, die den Koordinaten entlang der X-, Y- und Z-Achse im globalen Raum entsprechen.

Die Perlin-Rauschfunktion und der Zufallszahlengenerator im Rauschbeispiel erstellen eine Zufallszahl (Rauschen) basierend auf bis zu drei Eingabewerten, die globale Bezeichner für jedes Arbeitselement sein können. Es gibt einen anderen Algorithmus: Ein oder mehrere Werte können durch Kombinieren globaler Bezeichner und aller vom Kernel empfangenen oder erstellten Datenwerte erstellt werden.

Das folgende OpenCL-Kernel-Code-Snippet zeigt beispielsweise die Erstellung von Zufallszahlen basierend auf einer zweidimensionalen globalen Workitem-ID.

kernel void genRand()
{
	uint	x = get_global_id(0);
	uint	y = get_global_id(1);
	uint	rand_num = ParallelRNG2( x, y );
	...

Abbildung 1. Ein Beispiel für die Verwendung von Zufallszahlen (zwei Dimensionen)

Dieser Ansatz ermöglicht es, die Funktionen einer Zufallszahl oder eines Rauschgenerators parallel zwischen Arbeitselementen auszuführen, aber gleichzeitig Ergebnisse mit wiederholbaren Folgen von Werten zu erhalten, die zufällig zwischen Arbeitselementen und zwischen anderen Werten innerhalb desselben Arbeitselements variieren. Wenn Sie mehrere zweidimensionale Wertesätze erstellen müssen, können Sie dreidimensionale Erstellungsfunktionen verwenden: Die ersten beiden Eingabewerte werden aus den globalen Bezeichnern des Workitems und die dritte Dimension durch sukzessives Erhöhen eines Anfangswertes für jeden zusätzlichen erforderlichen Wert abgerufen. Dieser Algorithmus kann erweitert werden, um mit mehreren Sätzen dreidimensionaler Zufallswerte oder Rauschwerte zu arbeiten, wie im folgenden Beispiel mit Perlin-Rauschen.

kernel void multi2dNoise( float fScale, float offset )
{
float	fX = fScale * get_global_id(0);
float	fY = fScale * get_global_id(1);
float	fZ = offset;
float	randResult = Noise_3d(  fX,  fY,  fZ );
...

Abbildung 2. Ein Beispiel für die Verwendung des Perlin-Rauschalgorithmus (drei Dimensionen)

Einschränkungen


Die Funktionen Noise_2d und Noise_3d verwenden denselben grundlegenden Perlin-Rauschalgorithmus, die Implementierung unterscheidet sich jedoch aufgrund der Empfehlungen von Perlin. (Siehe den ersten Link in der Referenzliste.) Im Noise-Beispiel wird im Noise-Beispiel nur Noise_3d verwendet, und der Noise_2d-Testkernel ist in der Noise.cl-Datei für Leser enthalten, die dieses Beispiel ändern und damit experimentieren möchten.

Die Funktionen Noise_2d und Noise_3d sollten mit eingegebenen Gleitkommawerten aufgerufen werden. Die Werte müssen einen bestimmten Bereich abdecken, z. B. (0.0, 128.0), um die Größe der "Tabelle" (siehe Abbildung 3) von Zufallswerten anzugeben. Die Leser sollten sich das Cloud-Beispiel ansehen, um zu sehen, wie Perlins Rauschen in eine Vielzahl von „natürlich aussehenden“ Bildern umgewandelt werden kann.

Die im Zufallstest verwendete Standardfunktion ParallelRNG liefert zufällige (und ähnliche) Ergebnisse, ist jedoch nicht der schnellste Algorithmus zur Generierung von Zufallszahlen. Diese Funktion basiert auf dem „Wong-Hash“, der ursprünglich nicht als Zufallszahlengenerator gedacht war. Einige häufig verwendete Funktionen von Zufallszahlengeneratoren (ein auskommentiertes Beispiel in der Noise.cl-Datei) zeigten jedoch eine sichtbare Wiederholbarkeit beim Ausfüllen zweidimensionaler Bilder, insbesondere in Bits niedrigerer Ordnung der Ergebnisse. Leser können auch mit anderen (schnelleren) Zufallszahlengeneratorfunktionen experimentieren.

Die Standardfunktion ParallelRNG erzeugt als Ergebnis nur vorzeichenlose 32-Bit-Ganzzahlen. Wenn für einen Bereich wie (0.0, 1.0) Gleitkommawerte erforderlich sind, muss die Anwendung die Zuordnung für diesen Bereich anwenden. Das Zufallsmuster gleicht ein zufälliges vorzeichenloses Ganzzahlergebnis mit einem Bereich (0, 255) ab, um Graustufenpixelwerte zu erstellen. Zu diesem Zweck wird die binäre UND-Operation einfach angewendet, um 8 Bits auszuwählen.

Die Standardfunktion ParallelRNG erstellt nicht alle 4.294.967.296 (2 32) vorzeichenlose Ganzzahlwerte für aufeinanderfolgende Aufrufe basierend auf dem zuvor erstellten Wert. Für jeden einzelnen Anfangswert kann der Wert von Pseudozufallssequenzen (Zyklen) von insgesamt 7000 eindeutigen Werten bis zu ungefähr 2 Milliarden Werten reichen. Die ParallelRNG-Funktion erzeugt ungefähr 20 verschiedene Zyklen. Der Autor hält es für unwahrscheinlich, dass für ein Arbeitselement des OpenCL-Kernels mehr Zufallszahlen nacheinander generiert werden müssen, als in der kleinsten Schleife gebildet werden.

Die zweidimensionalen und dreidimensionalen Versionen dieser Funktion - ParallelRNG2 und ParallelRNG3 - verwenden Schleifenmischung, indem die binäre XOR-Operation zwischen dem Ergebnis des vorherigen ParallelRNG-Aufrufs und dem nächsten Eingabewert angewendet wird, wodurch sich die Länge der Zyklen ändert. Ein solches modifiziertes Verhalten wurde jedoch keiner detaillierten Analyse unterzogen. Daher wird dem Leser empfohlen, sorgfältig zu überprüfen, ob die ParallelRNG-Funktionen den Anforderungen der Anwendung entsprechen.

Projektstruktur


In diesem Abschnitt werden nur die Grundelemente des Quellcodes für die Beispielanwendung aufgeführt.

NoiseMain.cpp:


main ()

Haupteingabefunktion. Nach dem Parsen der Befehlszeilenparameter wird OpenCL initialisiert, das OpenCL-Programm aus der Datei Noise.cl kompiliert, einer der Kernel für den Start vorbereitet, ExecuteNoiseKernel () und anschließend ExecuteNoiseReference () aufgerufen . Nachdem überprüft wurde, ob diese beiden Implementierungen dieselben Ergebnisse liefern, gibt main () Informationen zur Betriebszeit jeder dieser Funktionen und speichert die aus ihrer Arbeit resultierenden Bilder.

ExecuteNoiseKernel ()

Konfigurieren Sie den ausgewählten Noise-Kernel und führen Sie ihn in OpenCL aus.

ExecuteNoiseReference ()

Konfigurieren Sie den ausgewählten Noise C-Referenzcode und führen Sie ihn aus.

Noise.cl:


defaut_perm [256]

Tabelle der Zufallswerte 0–255 für den Kern des dreidimensionalen Perlin-Rauschens. Um die Zufälligkeit weiter zu verbessern, kann diese Tabelle generiert und auf den Kern des Perlin-Rauschens übertragen werden.

grads2d [16]

16 gleichmäßig verteilte Einheitsvektoren, Gradienten für den Kern des zweidimensionalen Perlin-Rauschens.

grads3d [16]

16 Vektorgradienten für den Kern des dreidimensionalen Perlin-Rauschens.

ParallelRNG ()

Pseudozufallszahlengenerator, ein Durchgang für einen Eingabewert. Eine alternative Funktion des Zufallszahlengenerators wird auskommentiert, aber der Datei hinzugefügt, falls Leser eine Funktion testen möchten, die schneller funktioniert, aber schlechtere Ergebnisse liefert.

ParallelRNG2 ()

Ein Zufallszahlengenerator, der 2 Durchgänge durchführt, gibt 2 Eingabewerte aus.

ParallelRNG3 ()

Ein Zufallszahlengenerator, der 3 Durchgänge durchführt, gibt 3 Eingabewerte aus.

weight_poly3 (), weight_poly5 () und WEIGHT ()

Dies sind alternative Gewichtsfunktionen, die vom Perlin-Rauschalgorithmus verwendet werden, um die Kontinuität von Gradienten sicherzustellen. Die zweite (bevorzugte) Funktion stellt auch die Kontinuität der zweiten Ableitung sicher. Das WEIGHT-Makro wählt aus, welche Funktion verwendet wird.

NORM256 ()

Ein Makro, das den Bereich (0, 255) in (-1,0, 1,0) konvertiert.

interp ()

Bilineare Interpolation mit OpenCL.

hash_grad_dot2 ()

Wählt den Gradienten aus und berechnet das Skalarprodukt mit eingegebenen XY-Werten als Teil der Perlin-Rauschfunktion Noise_2d.

Noise_2d ()

Perlin Noise Generator mit zwei Eingangswerten.

hash_grad_dot3 ()

Wählt den Gradienten aus und berechnet das Skalarprodukt mit den eingegebenen XYZ-Werten als Teil der Perlin-Rauschfunktion Noise_3d.

Noise_3d ()

Perlin Noise Generator mit drei Eingangswerten.

Wolke ()

Erstellt mit Noise_3d ein Einzelpixel-Cloud-Ausgabebild für CloudTest.

map256 ()

Konvertiert den Ausgabebereich des Perlin-Rauschens (-1,0, 1,0) in den Bereich (0, 255), der zur Erzeugung von Graustufenpixeln erforderlich ist.

CloudTest ()

Cloud-Bildtest. Der Slice-Parameter wird an die Cloud-Funktion übergeben, damit der Systemcode andere Cloud-Images erstellen kann.

Noise2dTest ()

Der Noise_2d-Test wird standardmäßig nicht verwendet.

Noise3dTest ()

Test Noise_3d - Standardmäßig Perlin-Rauschfunktion. Verwendet map256, um Pixelwerte für Graustufenbilder abzurufen.

RandomTest ()

Der ParallelRNG3-Test verwendet das niedrigstwertige Byte eines vorzeichenlosen ganzzahligen Ergebnisses, um Graustufenbilder zu erzeugen.

Für die Visual Studio-Versionen 2012 und 2013 werden zwei Microsoft Visual Studio-Lösungsdateien bereitgestellt. Dies sind die Dateien Noise_2012.sln und Noise_2013.sln. Wenn der Reader eine neuere Version von Visual Studio verwendet, sollte es möglich sein, mithilfe der Funktion Visual Studio Solution und Project Update eine neue Lösung basierend auf diesen Dateien zu erstellen.
Beide Lösungen setzen voraus, dass Intel® OpenCL ™ Code Builder auf dem System installiert ist .

Probenverwaltung


Das Beispiel kann über die Microsoft Windows * -Eingabeaufforderung in dem Ordner ausgeführt werden, in dem sich die EXE-Datei befindet.
Noise.exe <параметры>


Parameter


-h или --help
Zeigt die Hilfe in der Befehlszeile an. Demonstrationen werden nicht ausgelöst. Wählen Sie den Gerätetyp aus, für den der OpenCL-Kernel ausgeführt wird. Der Standardwert ist all. <OpenCL-Gerätetypkonstante> Wählen Sie die zu verwendende Plattform aus. Wenn die Demo startet, wird eine Liste aller Plattformnummern und -namen angezeigt. [Ausgewählt] wird rechts von der verwendeten Plattform angezeigt. Geben Sie bei Verwendung einer Zeichenfolge genügend Buchstaben an, um den Plattformnamen eindeutig zu erkennen. Der Standardwert ist Intel. Wählen Sie das Gerät, auf dem OpenCL-Kerne ausgeführt werden, nach Nummer oder Name aus. Zu Beginn der Demonstration wird eine Liste aller Nummern und Gerätenamen auf der verwendeten Plattform angezeigt. Rechts vom aktuellen Gerät wird [Ausgewählt] angezeigt. Der Standardwert ist 0.

-t или --type [ all | cpu | gpu | acc | default | <константа типа устройства OpenCL>


CL_DEVICE_TYPE_ALL | CL_DEVICE_TYPE_CPU | CL_DEVICE_TYPE_GPU |
CL_DEVICE_TYPE_ACCELERATOR | CL_DEVICE_TYPE_DEFAULT


-p или --platform <число или строка>


-d или --device <число или строка>


-r или --run [ random | perlin | clouds ]
Выбор демонстрации функции для запуска. У генератора случайных чисел, алгоритма шума Перлина и генератора изображения облака есть демонстрационные ядра. Значение по умолчанию: random.

-s или --seed <целое число>
Целочисленное входное значение, в зависимости от которого изменяется результат работы алгоритма. Значение по умолчанию: 1.

Noise.exe выводит время работы ядра OpenCL и эталонного эквивалентного кода C, а также имена выходных файлов обоих алгоритмов. По завершении вывода информации программа ожидает нажатия пользователем клавиши ВВОД перед выходом. Обратите внимание, что функции эталонного кода на C не были оптимизированы, их цель состоит лишь в проверке правильности кода ядра OpenCL.

Анализ результатов


Durchsuchen Sie nach Abschluss von Noise.exe die BMP-Bilddateien OutputOpenCL.bmp und OutputReference.bmp im Arbeitsordner, um die Ergebnisse des OpenCL- und C ++ - Codes zu vergleichen. Diese beiden Bilder sollten gleich sein, obwohl sehr geringe Unterschiede zwischen den beiden Perlin-Rauschbildern oder zwischen den beiden Wolkenbildern möglich sind.
Das Ergebnis des Rauschalgorithmus (Perlin-Rauschen) sollte wie in Abbildung 3 aussehen.


Abbildung 3. Das Ergebnis des Perlin-Rauschalgorithmus

Das Ergebnis des Zufallsalgorithmus (Zufallszahlengenerator) sollte wie in Abbildung 4 aussehen.


Abbildung 4. Das Ergebnis des Zufallsgenerators Zahlen

Das Ergebnis der Operation des Cloud-Algorithmus sollte wie in Abbildung 5 dargestellt aussehen.


Abbildung 5. Das Ergebnis des Cloud-Erstellungsalgorithmus

Referenzen


  1. C. Perlin, K. "Noise Improvement"
  2. "Hashing 4-Byte-Ganzzahlen"
  3. M. Overton (MA), "Schnelle hochqualitative parallele Zufallszahlengeneratoren", Dr. Dobb's (2011)
  4. Implementierung und Verwendung der Intel® DRNG-Bibliothek (Digital Random Number Generator)
  5. Beispiel-Lizenzvereinbarung für Intel Software
  6. Intel® OpenCL ™ Code Builder

Jetzt auch beliebt: