Ich schreibe ein OpenCL-Programm zur Verwendung mit meiner GPU der AMD Radeon HD 7800-Serie. Laut AMDs OpenCL-Programmierhandbuch verfügt diese GPU-Generation über zwei Hardware-Warteschlangen, die asynchron arbeiten können.
5.5.6 Befehlswarteschlange
Für Südinseln und höher unterstützen Geräte mindestens zwei Hardware-Rechenwarteschlangen. Dadurch kann eine Anwendung den Durchsatz kleiner Dispatches mit zwei Befehlswarteschlangen für die asynchrone Übermittlung und möglicherweise Ausführung erhöhen. Die Hardware-Berechnungswarteschlangen werden in der folgenden Reihenfolge ausgewählt: Erste Warteschlange = gerade OCL-Befehlswarteschlangen, zweite Warteschlange = ungerade OCL-Warteschlangen.
Zu diesem Zweck habe ich zwei separate OpenCL-Befehlswarteschlangen erstellt, um Daten an die GPU weiterzuleiten. Das Programm, das auf dem Host-Thread ausgeführt wird, sieht ungefähr so aus:
static const int kNumQueues = 2;
cl_command_queue default_queue;
cl_command_queue work_queue[kNumQueues];
static const int N = 256;
cl_mem gl_buffers[N];
cl_event finish_events[N];
clEnqueueAcquireGLObjects(default_queue, gl_buffers, N);
int queue_idx = 0;
for (int i = 0; i < N; ++i) {
cl_command_queue queue = work_queue[queue_idx];
cl_mem src = clCreateBuffer(CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, ...);
// Enqueue a few kernels
cl_mem tmp1 = clCreateBuffer(CL_READ_WRITE);
clEnqueueNDRangeKernel(kernel1, queue, src, tmp1);
clEnqueueNDRangeKernel(kernel2, queue, tmp1, tmp1);
cl_mem tmp2 = clCreateBuffer(CL_READ_WRITE);
clEnqueueNDRangeKernel(kernel2, queue, tmp1, tmp2);
clEnqueueNDRangeKernel(kernel3, queue, tmp2, gl_buffer[i], finish_events + i);
queue_idx = (queue_idx + 1) % kNumQueues;
}
clEnqueueReleaseGLObjects(default_queue, gl_buffers, N);
clWaitForEvents(N, finish_events);
Mit kNumQueues = 1
funktioniert diese Anwendung so ziemlich wie beabsichtigt: Sie sammelt die gesamte Arbeit in einer einzigen Befehlswarteschlange, die dann vollständig ausgeführt wird, wobei die GPU die ganze Zeit über ziemlich beschäftigt ist. Ich kann dies anhand der Ausgabe des CodeXL-Profilers sehen:
Wenn ich jedoch einstelle kNumQueues = 2
, erwarte ich, dass dasselbe passiert, aber die Arbeit gleichmäßig auf zwei Warteschlangen verteilt ist. Wenn überhaupt, erwarte ich, dass jede Warteschlange die gleichen Eigenschaften wie die eine Warteschlange hat: Sie beginnt nacheinander zu arbeiten, bis alles erledigt ist. Wenn Sie jedoch zwei Warteschlangen verwenden, kann ich feststellen, dass nicht die gesamte Arbeit auf die beiden Hardware-Warteschlangen aufgeteilt ist:
Zu Beginn der Arbeit der GPU können die Warteschlangen einige Kernel asynchron ausführen, obwohl es so aussieht, als ob keiner der beiden die Hardware-Warteschlangen jemals vollständig belegt (es sei denn, mein Verständnis ist falsch). Gegen Ende der GPU-Arbeit scheinen die Warteschlangen nur einer der Hardware-Warteschlangen nacheinander Arbeit hinzuzufügen, aber es gibt sogar Zeiten, in denen keine Kernel ausgeführt werden. Was gibt? Habe ich ein grundlegendes Missverständnis darüber, wie sich die Laufzeit verhalten soll?
Ich habe einige Theorien, warum dies geschieht:
Die eingestreuten
clCreateBuffer
Aufrufe zwingen die GPU, Geräteressourcen aus einem gemeinsam genutzten Speicherpool synchron zuzuweisen, wodurch die Ausführung einzelner Kernel blockiert wird.Die zugrunde liegende OpenCL-Implementierung ordnet logische Warteschlangen keinen physischen Warteschlangen zu und entscheidet nur, wo Objekte zur Laufzeit platziert werden sollen.
Da ich GL-Objekte verwende, muss die GPU beim Schreiben den Zugriff auf den speziell zugewiesenen Speicher synchronisieren.
Stimmt eine dieser Annahmen? Weiß jemand, was dazu führen könnte, dass die GPU im Szenario mit zwei Warteschlangen wartet? Jeder Einblick wäre dankbar!