Ich werde die OCL-Kernleistung verbessern und klären, wie Speichertransaktionen funktionieren und welches Speicherzugriffsmuster wirklich besser ist (und warum). Der Kernel wird mit Vektoren von 8 ganzen Zahlen gespeist, die als Array definiert sind: int v [8], das heißt, bevor irgendeine Berechnung durchgeführt wird, muss der gesamte Vektor in GPRs geladen werden. Also, ich glaube, der Flaschenhals dieses Codes ist die anfängliche Datenlast.OpenCL (AMD GCN) globales Speicherzugriffsmuster für vektorisierte Daten: fortlaufend vs. zusammenhängend
Zuerst betrachte ich einige theoretische Grundlagen.
Ziel-HW ist Radeon RX 480/580, das über 256-Bit-GDDR5-Speicherbus verfügt, auf dem die Burst-Lese-/Schreibtransaktion eine Granularität von 8 Wörtern aufweist. Daher liest eine Speichertransaktion 2048 Bit oder 256 Byte. Das, glaube ich, was CL_DEVICE_MEM_BASE_ADDR_ALIGN bezieht sich auf:
Alignment (bits) of base address: 2048.
So meine erste Frage: Was ist der physikalische Sinn von 128-Byte-Cache-Line? Behält es den Teil der Daten bei, der von einem einzelnen Burst gelesen, aber nicht wirklich angefordert wurde? Was passiert mit dem Rest, wenn wir zum Beispiel 32 oder 64 Bytes angefordert haben - also übersteigt der Rest die Cache-Zeilengröße? (Ich nehme an, es wird nur verworfen - dann, welcher Teil: Kopf, Schwanz ...?)
Nun zurück zu meinem Kernel, ich denke, dass Cache in meinem Fall keine signifikante Rolle spielt, weil ein Burst liest 64 Ganzzahlen -> Eine Speichertransaktion kann theoretisch 8 Arbeitsaufgaben gleichzeitig liefern, es sind keine zusätzlichen Daten zu lesen und der Speicher wird immer zusammengeführt.
Aber dennoch kann ich meine Daten mit zwei unterschiedlichen Zugriffsmustern platzieren:
1) angrenzende
a[i] = v[get_global_id(0) * get_global_size(0) + i];
(Weiche als eigentlich perfomed)
*(int8*)a = *(int8*)v;
2) verschachtelt
a[i] = v[get_global_id(0) + i * get_global_size(0)];
Ich erwarte in meinem Fall zusammenhängend wäre schneller, weil wie oben erwähnt eine Speichertransaktion vollständig 8 Arbeitsaufgaben mit Daten stopfen kann. Allerdings weiß ich nicht, wie der Scheduler in der Recheneinheit physikalisch funktioniert: Müssen alle Daten für alle SIMD-Lanes bereit sein oder reicht der erste Teil für 4 parallele SIMD-Elemente? Nichtsdestoweniger nehme ich an, dass es schlau genug ist, zuerst mindestens eine CU mit Daten zu versorgen, sobald die CUs die Befehlsströme unabhängig ausführen können. Während im zweiten Fall müssen wir 8 * global_size/64 Transaktionen durchführen, um einen vollständigen Vektor zu erhalten.
Also, meine zweite Frage: ist meine Annahme richtig?
Nun, die Praxis.
Eigentlich habe ich die gesamte Aufgabe in zwei Kerne aufgeteilt, weil ein Teil weniger Registerdruck hat als ein anderes und daher mehr Arbeitsaufgaben übernehmen kann. Also spielte ich zuerst mit pattern, wie die Daten im Übergang zwischen den Kerneln gespeichert (mit vload8/vstore8 oder Casting zu int8 das gleiche Ergebnis) und das Ergebnis war etwas seltsam: Kernel, der Daten in zusammenhängender Weise liest arbeitet etwa 10% schneller (beide in CodeXL und durch OS Zeitmessung), aber der Kernel, der Daten zusammenhängend speichert, führt überraschend langsamer. Die Gesamtzeit für zwei Kernel ist dann ungefähr gleich. In meinen Gedanken müssen sich beide mindestens genauso verhalten - entweder langsamer oder schneller, aber diese umgekehrten Ergebnisse schienen unerklärlich.
Und meine dritte Frage ist: kann jemand solch ein Ergebnis erklären? Oder kann ich etwas falsch machen? (Oder ganz falsch?)
Vielen Dank für die Antwort. Ich spreche jedoch nicht über einen verschachtelten vs verschachtelten Zugriff. Mag sein, dass meine Schriften nicht so klar sind, aber der Zugang ist immer zusammengewachsen - der Unterschied besteht nur darin, die Daten vektorweise vs. elementweise zu lesen. Ich habe die Frage korrigiert, um ein bisschen zu verdeutlichen. – qpdb
@qpdb die Sache, die Sie zusammenhängend nannten, ist vom Gesichtspunkt des Kerns zusammenhängend und wird vom Gesichtspunkt des Gedächtnisses zu einem gegebenen Zyklus verschachtelt, also kann das Lesen des ersten Elements jedes workitem restliche Daten cachen. Aber beim Schreiben gibt es dieses Verhalten nicht, also wird es langsamer.Die Sache, die Sie "verschachtelt" genannt haben, ist in einem gegebenen Zyklus für den Speicher tatsächlich zusammenhängend, weil der Lese/Schreib-Scheduler (oder welcher Teil auch immer liest/schreibt) n Arbeitselementen dienen kann, die über eine große Reihe von benachbarten Elementen einheitlich lesen/schreiben. –
BTW, nochmals vielen Dank für die Dokumentation. Ich habe von dort gelernt: "Southern Island-Geräte unterstützen keine zusammengewachsenen Schreibvorgänge; kontinuierliche Adressen innerhalb von Arbeitsgruppen bieten jedoch maximale Leistung." Diese Information sieht doppelt seltsam aus, da meine Experimente ein völlig anderes Ergebnis ergeben. Oder verstehe ich den ganzen Begriff des "Verschmelzens" ganz im Gegenteil? – qpdb