Obwohl die arbeitseffiziente Version mehr Schritte erfordert, wird dies durch die Tatsache ausgeglichen, dass die Anzahl der aktiven Threads schneller abnimmt und die Gesamtzahl der aktiven Threads über alle Iterationen erheblich kleiner ist. Wenn ein Warp während einer Iteration keine aktiven Threads hat, springt dieser Warp einfach zur folgenden Barriere und wird angehalten, sodass andere Warps ausgeführt werden können. Wenn Sie also weniger aktive Warps haben, macht sich dies häufig in der Ausführungszeit bezahlt. (Dies impliziert, dass der GPU-Code so entworfen werden muss, dass aktive Threads in möglichst wenigen Warps zusammengefasst werden. Sie möchten nicht, dass sie nur spärlich verstreut sind, da selbst ein aktiver Thread den gesamten Warp erzwingt aktiv bleiben.)
Berücksichtigen Sie die Anzahl der aktiven Threads im naiven Algorithmus. In Abbildung 2 des Artikels sehen Sie, dass alle Threads mit Ausnahme der ersten 2 k in der k- ten Iteration aktiv sind. Bei N Threads beträgt die Anzahl der aktiven Threads also N - 2 k . Mit N = 1024 beträgt die Anzahl der aktiven Threads pro Iteration beispielsweise:
1023, 1022, 1020, 1016, 1008, 992, 960, 896, 768, 512
Wenn ich dies in die Anzahl der aktiven Warps umwandle (durch Teilen durch 32 und Aufrunden), erhalte ich:
32, 32, 32, 32, 32, 31, 30, 28, 24, 16
289. Andererseits beginnt der arbeitseffiziente Algorithmus mit halb so vielen Threads, halbiert dann die Anzahl der aktiven Threads bei jeder Iteration, bis er auf 1 sinkt, und beginnt dann zu verdoppeln, bis er wieder auf 1 sinkt nochmal die halbe Arraygröße:
512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512
Umwandlung in aktive Warps:
16, 8, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 8, 16
Die Summe ist 71, das ist nur ein Viertel so viel. Sie können also sehen, dass die Anzahl der aktiven Verzerrungen im Verlauf des gesamten Vorgangs mit dem arbeitseffizienten Algorithmus viel geringer ist. (Tatsächlich gibt es für einen längeren Lauf in der Mitte nur eine Handvoll aktiver Warps, was bedeutet, dass der größte Teil des Chips nicht belegt ist. Wenn zusätzliche Rechenaufgaben ausgeführt werden, z. B. von anderen CUDA-Streams, können sie erweitert werden, um diese zu füllen unbesetzter Raum.)