Erste Schritte mit GPU-Computing im Web

In diesem Beitrag wird die experimentelle WebGPU API anhand von Beispielen erläutert. beginnen Sie mit datenparallelen Berechnungen mit der GPU.

François Beaufort
François Beaufort

Hintergrund

Wie Sie vielleicht schon wissen, ist der Grafikprozessor (Graphic Processing Unit, GPU) ein elektronischer Subsystem eines Computers, der ursprünglich auf die Verarbeitung spezialisiert war, Grafiken. In den letzten 10 Jahren hat sie sich jedoch zu einer flexibleren Architektur, mit der Entwickler viele Arten von Algorithmen implementieren können, 3D-Grafiken rendern und dabei die einzigartige Architektur der GPU Diese Funktionen werden als GPU-Computing bezeichnet. Dabei wird eine GPU Der Koprozessor für das wissenschaftliche Rechnen für allgemeine Zwecke wird als GPU-Programmierung (GPGPU)

GPU-Computing hat wesentlich zum aktuellen Boom des maschinellen Lernens da Convolution Neural Networks und andere Modelle für eine effizientere Ausführung auf GPUs. Mit der aktuellen Webplattform die mangelnde GPU-Computing-Funktionalität, die "GPU für das Web" des W3C Community-Gruppe entwickelt eine API, um die modernen GPU-APIs bereitzustellen, die auf den meisten aktuellen Geräten. Diese API wird als WebGPU bezeichnet.

WebGPU ist ein Low-Level-API wie WebGL. Sie ist sehr wirkungsvoll und sehr ausführlich, die Sie sehen werden. Das ist auch vollkommen in Ordnung. Wir achten auf die Leistung.

In diesem Artikel werde ich mich auf den GPU-Computing-Teil von WebGPU konzentrieren und Ehrlich gesagt, ich kratzer nur an der Oberfläche, damit ihr gleich auf eurem gehören. Ich werde tiefer in das WebGPU-Rendering (Canvas, Textur, künftige Artikel.

Auf GPU zugreifen

Der Zugriff auf die GPU ist über WebGPU einfach. navigator.gpu.requestAdapter() wird angerufen gibt ein JavaScript-Promise zurück, das asynchron mit einer GPU aufgelöst wird Adapter. Stellen Sie sich diesen Adapter als Grafikkarte vor. Sie können sie entweder (auf demselben Chip wie die CPU) oder diskret (normalerweise eine PCIe-Karte, ist aber leistungsfähiger, verbraucht jedoch mehr Energie).

Sobald Sie den GPU-Adapter haben, rufen Sie adapter.requestDevice() auf, um ein Promise zu erhalten das mit einem GPU-Gerät aufgelöst wird, das Sie für GPU-Berechnungen verwenden.

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();

Beide Funktionen bieten Optionen, mit denen Sie genau angeben können, Netzteil (Stromeinstellung) und das gewünschte Gerät (Erweiterungen, Limits) ein. Für die Der Einfachheit halber verwenden wir in diesem Artikel die Standardoptionen.

Zwischenspeicher schreiben

Sehen wir uns an, wie Sie mit JavaScript Daten für die GPU in den Arbeitsspeicher schreiben. Dieses Der Prozess ist aufgrund des Sandbox-Modells, das im modernen Web verwendet wird, nicht ganz einfach. Browser.

Das folgende Beispiel zeigt, wie Sie vier Byte in den Pufferspeicher schreiben, auf den zugegriffen werden kann. von der GPU. Sie ruft device.createBuffer() auf, wobei die Größe des und dessen Nutzung. Auch wenn das Nutzungs-Flag GPUBufferUsage.MAP_WRITE für diesen speziellen Aufruf nicht erforderlich ist, sollten wir ausdrücklich darauf hinweisen, in diesen Puffer ein. Dies führt zu einem GPU-Zwischenspeicherobjekt, das bei der Erstellung mappedAtCreation auf „true“ gesetzt. Dann kann der zugehörige Zwischenspeicher der binären Rohdaten durch Aufrufen der GPU-Puffermethode getMappedRange() abgerufen werden.

Das Schreiben von Byte ist vertraut, wenn Sie bereits mit ArrayBuffer gespielt haben. eine TypedArray und kopieren Sie die Werte hinein.

// Get a GPU buffer in a mapped state and an arrayBuffer for writing.
const gpuBuffer = device.createBuffer({
  mappedAtCreation: true,
  size: 4,
  usage: GPUBufferUsage.MAP_WRITE
});
const arrayBuffer = gpuBuffer.getMappedRange();

// Write bytes to buffer.
new Uint8Array(arrayBuffer).set([0, 1, 2, 3]);

An dieser Stelle ist der GPU-Zwischenspeicher zugeordnet, d. h. er gehört der CPU. kann über Lesen/Schreiben aus JavaScript aufgerufen werden. Damit die GPU darauf zugreifen kann, muss die Zuordnung aufgehoben werden. Dazu müssen Sie lediglich gpuBuffer.unmap() aufrufen.

Das Konzept der Zuordnung/Nichtzuordnung ist erforderlich, um Race-Bedingungen zu vermeiden, und CPU-Zugriff auf den Arbeitsspeicher.

Zwischenspeicher lesen

Sehen wir uns nun an, wie wir einen GPU-Zwischenspeicher in einen anderen GPU-Zwischenspeicher kopieren und auslesen können.

Da wir in den ersten GPU-Zwischenspeicher schreiben, GPU-Zwischenspeicher, neues Nutzungs-Flag GPUBufferUsage.COPY_SRC ist erforderlich. Die zweite Der GPU-Zwischenspeicher wird diesmal in einem nicht zugeordneten Status mit device.createBuffer() Das Nutzungs-Flag ist GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, da es als Ziel der ersten GPU verwendet wird nach Ausführung der GPU-Kopierbefehle in JavaScript zu puffern und zu lesen.

// Get a GPU buffer in a mapped state and an arrayBuffer for writing.
const gpuWriteBuffer = device.createBuffer({
  mappedAtCreation: true,
  size: 4,
  usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC
});
const arrayBuffer = gpuWriteBuffer.getMappedRange();

// Write bytes to buffer.
new Uint8Array(arrayBuffer).set([0, 1, 2, 3]);

// Unmap buffer so that it can be used later for copy.
gpuWriteBuffer.unmap();

// Get a GPU buffer for reading in an unmapped state.
const gpuReadBuffer = device.createBuffer({
  size: 4,
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});

Da die GPU ein unabhängiger Koprozessor ist, werden alle GPU-Befehle ausgeführt asynchron programmiert. Aus diesem Grund wird eine Liste mit GPU-Befehlen erstellt und bei Bedarf Batches ausführen. In WebGPU hat der GPU-Befehlsencoder, der von device.createCommandEncoder() ist das JavaScript-Objekt, das einen Batch von "gepuffert" die irgendwann an die GPU gesendet werden. Die Methoden auf GPUBuffer hingegen sind „nicht gepuffert“, d. h. sie werden atomar ausgeführt zum Zeitpunkt ihres Anrufs.

Wenn du den GPU-Befehlsencoder hast, rufe copyEncoder.copyBufferToBuffer() auf. wie unten gezeigt, um diesen Befehl zur späteren Ausführung zur Befehlswarteschlange hinzuzufügen. Schließe zum Abschluss die Codierungsbefehle ab, indem du copyEncoder.finish() aufrufst und an die Befehlswarteschlange des GPU-Geräts. Die Warteschlange ist für die Verarbeitung Einreichungen über device.queue.submit() mit den GPU-Befehlen als Argumente. Dadurch werden alle im Array gespeicherten Befehle in kleinstmöglicher Reihenfolge ausgeführt.

// Encode commands for copying buffer to buffer.
const copyEncoder = device.createCommandEncoder();
copyEncoder.copyBufferToBuffer(
  gpuWriteBuffer /* source buffer */,
  0 /* source offset */,
  gpuReadBuffer /* destination buffer */,
  0 /* destination offset */,
  4 /* size */
);

// Submit copy commands.
const copyCommands = copyEncoder.finish();
device.queue.submit([copyCommands]);

Bisher wurden GPU-Warteschlangenbefehle gesendet, aber nicht unbedingt ausgeführt. Rufen Sie zum Lesen des zweiten GPU-Zwischenspeichers gpuReadBuffer.mapAsync() mit GPUMapMode.READ. Sie gibt ein Promise zurück, das aufgelöst wird, wenn der GPU-Zwischenspeicher zugeordnet. Dann rufen Sie den zugeordneten Bereich mit gpuReadBuffer.getMappedRange() ab, enthält dieselben Werte wie der erste GPU-Zwischenspeicher, sobald alle GPU-Befehle in der Warteschlange ausgeführt wurden.

// Read buffer.
await gpuReadBuffer.mapAsync(GPUMapMode.READ);
const copyArrayBuffer = gpuReadBuffer.getMappedRange();
console.log(new Uint8Array(copyArrayBuffer));

Sie können dieses Beispiel ausprobieren.

Kurz gesagt: Was Sie in Bezug auf Zwischenspeichervorgänge beachten müssen:

  • Die Zuordnung von GPU-Zwischenspeichern muss aufgehoben werden, damit sie bei der Einreichung von Gerätewarteschlangen verwendet werden können.
  • Bei der Zuordnung können GPU-Zwischenspeicher in JavaScript gelesen und geschrieben werden.
  • GPU-Zwischenspeicher werden zugeordnet, wenn mapAsync() und createBuffer() mit mappedAtCreation, die auf „true“ gesetzt sind, werden aufgerufen.

Shader-Programmierung

Programme, die auf der GPU ausgeführt werden und nur Berechnungen (und keine Dreiecken) werden als Compute-Shader bezeichnet. Sie werden von Hunderten Nutzern parallel ausgeführt. an GPU-Kernen (die kleiner als CPU-Kerne sind), die zusammen zur Verarbeitung Daten. Ihre Ein- und Ausgabe sind Puffer in einer WebGPU.

Um die Verwendung von Compute-Shadern in WebGPU zu veranschaulichen, werden wir mit der Matrixstruktur Multiplikation, ein gängiger Algorithmus beim maschinellen Lernen, der unten dargestellt ist.

<ph type="x-smartling-placeholder">
</ph> Matrixmultiplikationsdiagramm
Matrixmultiplikationsdiagramm

Hier ist unsere Vorgehensweise:

  1. Erstellen Sie drei GPU-Zwischenspeicher (zwei für die Matrizen zum Multiplizieren und einen für die Ergebnismatrix)
  2. Eingabe und Ausgabe für den Compute-Shader beschreiben
  3. Compute Shader-Code kompilieren
  4. Computing-Pipeline einrichten
  5. Codierte Befehle im Batch an die GPU senden
  6. Ergebnismatrix-GPU-Zwischenspeicher lesen

GPU-Puffererstellung

Matrizen werden der Einfachheit halber als Liste Punktzahlen. Das erste Element ist die Anzahl der Zeilen, das zweite Element Spaltenanzahl und der Rest sind die tatsächlichen Zahlen der Matrix.

<ph type="x-smartling-placeholder">
</ph> Einfache Darstellung einer Matrix in JavaScript und deren Entsprechung in der mathematischen Notation
Einfache Darstellung einer Matrix in JavaScript und deren Entsprechung in der mathematischen Notation

Die drei GPU-Zwischenspeicher sind Speicherpuffer, da wir Daten in den Compute-Shader. Dies erklärt, warum die Flags für die Nutzung von GPU-Puffern Folgendes umfassen: GPUBufferUsage.STORAGE für alle. Das Flag für die Ergebnismatrix-Verwendung enthält ebenfalls GPUBufferUsage.COPY_SRC, da es in einen anderen Zwischenspeicher kopiert wird für nachdem alle GPU-Warteschlangenbefehle ausgeführt wurden.

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();


// First Matrix

const firstMatrix = new Float32Array([
  2 /* rows */, 4 /* columns */,
  1, 2, 3, 4,
  5, 6, 7, 8
]);

const gpuBufferFirstMatrix = device.createBuffer({
  mappedAtCreation: true,
  size: firstMatrix.byteLength,
  usage: GPUBufferUsage.STORAGE,
});
const arrayBufferFirstMatrix = gpuBufferFirstMatrix.getMappedRange();
new Float32Array(arrayBufferFirstMatrix).set(firstMatrix);
gpuBufferFirstMatrix.unmap();


// Second Matrix

const secondMatrix = new Float32Array([
  4 /* rows */, 2 /* columns */,
  1, 2,
  3, 4,
  5, 6,
  7, 8
]);

const gpuBufferSecondMatrix = device.createBuffer({
  mappedAtCreation: true,
  size: secondMatrix.byteLength,
  usage: GPUBufferUsage.STORAGE,
});
const arrayBufferSecondMatrix = gpuBufferSecondMatrix.getMappedRange();
new Float32Array(arrayBufferSecondMatrix).set(secondMatrix);
gpuBufferSecondMatrix.unmap();


// Result Matrix

const resultMatrixBufferSize = Float32Array.BYTES_PER_ELEMENT * (2 + firstMatrix[0] * secondMatrix[1]);
const resultMatrixBuffer = device.createBuffer({
  size: resultMatrixBufferSize,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
});

Layout der Gruppe binden und Gruppe binden

Die Konzepte für das Layout und die Bindungsgruppe der Bindung gelten nur für WebGPU. Bindung Das Gruppenlayout definiert die Eingabe-/Ausgabeschnittstelle, die von einem Shader erwartet wird, während ein Bindungsgruppe stellt die tatsächlichen Eingabe-/Ausgabedaten für einen Shader dar.

Im folgenden Beispiel erwartet das Layout der Bindungsgruppe zwei schreibgeschützte Speicherzwischenspeicher, nummerierte Eintragbindungen 0, 1 und ein Speicherpuffer unter 2 für den Compute-Shader. Die Bindungsgruppe hingegen, die für dieses Layout der Bindungsgruppe definiert ist, verbindet GPU puffert die Einträge: gpuBufferFirstMatrix für die Bindung 0 gpuBufferSecondMatrix zur Bindung 1 und resultMatrixBuffer zur Bindung Bindung 2.

const bindGroupLayout = device.createBindGroupLayout({
  entries: [
    {
      binding: 0,
      visibility: GPUShaderStage.COMPUTE,
      buffer: {
        type: "read-only-storage"
      }
    },
    {
      binding: 1,
      visibility: GPUShaderStage.COMPUTE,
      buffer: {
        type: "read-only-storage"
      }
    },
    {
      binding: 2,
      visibility: GPUShaderStage.COMPUTE,
      buffer: {
        type: "storage"
      }
    }
  ]
});

const bindGroup = device.createBindGroup({
  layout: bindGroupLayout,
  entries: [
    {
      binding: 0,
      resource: {
        buffer: gpuBufferFirstMatrix
      }
    },
    {
      binding: 1,
      resource: {
        buffer: gpuBufferSecondMatrix
      }
    },
    {
      binding: 2,
      resource: {
        buffer: resultMatrixBuffer
      }
    }
  ]
});

Shader-Code berechnen

Der Compute-Shader-Code zum Multiplizieren von Matrizen ist in WGSL geschrieben: WebGPU Shader Language, das sich einfach in SPIR-V übersetzen lässt. Ohne Unter den drei angegebenen Speicherpuffern finden Sie mit var<storage>. Das Programm verwendet firstMatrix und secondMatrix als Eingaben und resultMatrix als Ausgabe.

Beachten Sie, dass für jeden Speicherzwischenspeicher ein binding-Element verwendet wird, das denselben Index, der in den Bindungsgruppenlayouts und den oben deklarierten Bindungsgruppen definiert ist.

const shaderModule = device.createShaderModule({
  code: `
    struct Matrix {
      size : vec2f,
      numbers: array<f32>,
    }

    @group(0) @binding(0) var<storage, read> firstMatrix : Matrix;
    @group(0) @binding(1) var<storage, read> secondMatrix : Matrix;
    @group(0) @binding(2) var<storage, read_write> resultMatrix : Matrix;

    @compute @workgroup_size(8, 8)
    fn main(@builtin(global_invocation_id) global_id : vec3u) {
      // Guard against out-of-bounds work group sizes
      if (global_id.x >= u32(firstMatrix.size.x) || global_id.y >= u32(secondMatrix.size.y)) {
        return;
      }

      resultMatrix.size = vec2(firstMatrix.size.x, secondMatrix.size.y);

      let resultCell = vec2(global_id.x, global_id.y);
      var result = 0.0;
      for (var i = 0u; i < u32(firstMatrix.size.y); i = i + 1u) {
        let a = i + resultCell.x * u32(firstMatrix.size.y);
        let b = resultCell.y + i * u32(secondMatrix.size.y);
        result = result + firstMatrix.numbers[a] * secondMatrix.numbers[b];
      }

      let index = resultCell.y + resultCell.x * u32(secondMatrix.size.y);
      resultMatrix.numbers[index] = result;
    }
  `
});

Pipelineeinrichtung

Die Computing-Pipeline ist das Objekt, das den Computing-Vorgang beschreibt. die wir ausführen werden. Rufen Sie zum Erstellen device.createComputePipeline() auf. Dafür sind zwei Argumente erforderlich: das zuvor erstellte Layout der Bindungsgruppe und ein Phase, in der der Einstiegspunkt unseres Compute-Shaders definiert wird (die WGSL-Funktion main) und dem tatsächlichen Compute-Shader-Modul, das mit device.createShaderModule() erstellt wurde.

const computePipeline = device.createComputePipeline({
  layout: device.createPipelineLayout({
    bindGroupLayouts: [bindGroupLayout]
  }),
  compute: {
    module: shaderModule,
    entryPoint: "main"
  }
});

Einreichung von Befehlen

Nach der Instanziierung einer Bindungsgruppe mit unseren drei GPU-Zwischenspeichern und einem mit einem Bindungsgruppenlayout verwenden, ist es an der Zeit, sie zu verwenden.

Starten wir einen programmierbaren Compute Pass-Encoder mit commandEncoder.beginComputePass() Wir codieren damit GPU-Befehle die die Matrixmultiplikation durchführt. Pipeline festlegen mit passEncoder.setPipeline(computePipeline) und seine Bindungsgruppe bei Index 0 mit passEncoder.setBindGroup(0, bindGroup). Der Index 0 entspricht dem group(0) im WGSL-Code.

Sprechen wir nun darüber, wie dieser Compute-Shader auf der GPU ausgeführt wird. Unsere dieses Programm für jede Zelle der Ergebnismatrix gleichzeitig auszuführen, Schritt für Schritt. Für eine Ergebnismatrix der Größe 16 x 32, um beispielsweise Im Ausführungsbefehl würden wir auf einem @workgroup_size(8, 8) passEncoder.dispatchWorkgroups(2, 4) oder passEncoder.dispatchWorkgroups(16 / 8, 32 / 8). Das erste Argument „x“ ist die erste Dimension, die zweite „y“. ist die zweite Dimension, und das letzte „z“ ist die dritte Dimension, die standardmäßig den Wert 1 hat, da sie hier nicht benötigt wird. In der GPU-Computing-Welt wird das Codieren eines Befehls zum Ausführen einer Kernel-Funktion für einen Datensatz als Weiterleitungsfunktion bezeichnet.

<ph type="x-smartling-placeholder">
</ph> Parallele Ausführung für jede Ergebnismatrix-Zelle
Parallele Ausführung für jede Ergebnismatrix-Zelle

Die Größe des Arbeitsgruppenrasters für unseren Compute-Shader beträgt in unserer WGSL (8, 8) Code. Aus diesem Grund wird „x“ und „y“ die jeweils der Anzahl der Zeilen werden die erste Matrix und die Anzahl der Spalten der zweiten Matrix bis zum 8. Damit können wir jetzt einen Compute-Aufruf mit passEncoder.dispatchWorkgroups(firstMatrix[0] / 8, secondMatrix[1] / 8) Die Anzahl der Arbeitsgruppenraster, die ausgeführt werden sollen, sind die Argumente dispatchWorkgroups().

Wie in der obigen Zeichnung zu sehen, hat jeder Shader Zugriff auf einen eindeutigen builtin(global_invocation_id)-Objekt, das als Ergebnis verwendet wird Matrixzelle zu berechnen.

const commandEncoder = device.createCommandEncoder();

const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);
const workgroupCountX = Math.ceil(firstMatrix[0] / 8);
const workgroupCountY = Math.ceil(secondMatrix[1] / 8);
passEncoder.dispatchWorkgroups(workgroupCountX, workgroupCountY);
passEncoder.end();

Rufe passEncoder.end() auf, um den Encoder für die Berechnung von Karten/Tickets zu beenden. Erstellen Sie dann GPU-Zwischenspeicher, der als Ziel zum Kopieren des Ergebnismatrix-Zwischenspeichers verwendet werden soll copyBufferToBuffer Beenden Sie die Codierungsbefehle mit copyEncoder.finish() und senden Sie diese an die GPU-Gerätewarteschlange, indem Sie Folgendes aufrufen: device.queue.submit() durch die GPU-Befehle.

// Get a GPU buffer for reading in an unmapped state.
const gpuReadBuffer = device.createBuffer({
  size: resultMatrixBufferSize,
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});

// Encode commands for copying buffer to buffer.
commandEncoder.copyBufferToBuffer(
  resultMatrixBuffer /* source buffer */,
  0 /* source offset */,
  gpuReadBuffer /* destination buffer */,
  0 /* destination offset */,
  resultMatrixBufferSize /* size */
);

// Submit GPU commands.
const gpuCommands = commandEncoder.finish();
device.queue.submit([gpuCommands]);

Ergebnismatrix lesen

Das Lesen der Ergebnismatrix ist so einfach wie das Aufrufen von gpuReadBuffer.mapAsync() mit GPUMapMode.READ und warten, bis das zurückgegebene Versprechen aufgelöst wird. Dies weist darauf hin, ist der GPU-Zwischenspeicher zugeordnet. An dieser Stelle ist es möglich, die Kartendaten Bereich mit gpuReadBuffer.getMappedRange().

<ph type="x-smartling-placeholder">
</ph> Ergebnis der Matrixmultiplikation
Matrixmultiplikationsergebnis

In unserem Code wird in der JavaScript-Konsole der Entwicklertools das Ergebnis „2, 2, 50, 60, 114, 140".

// Read buffer.
await gpuReadBuffer.mapAsync(GPUMapMode.READ);
const arrayBuffer = gpuReadBuffer.getMappedRange();
console.log(new Float32Array(arrayBuffer));

Glückwunsch! Sie haben es geschafft Sie können mit dem Beispiel experimentieren.

Ein letzter Trick

Eine Möglichkeit, Ihren Code leichter lesbar zu machen, ist die Verwendung der praktischen Methode getBindGroupLayout der Compute-Pipeline zum Ableiten der Bindungsgruppe aus dem Shader-Modul. Mit diesem Trick ist es nicht mehr erforderlich, Layout für benutzerdefiniertes Bindungsgruppen und Angeben eines Pipelinelayouts in Ihrem Computing wie Sie unten sehen können.

Eine Abbildung von getBindGroupLayout für das vorherige Beispiel ist verfügbar.

 const computePipeline = device.createComputePipeline({
-  layout: device.createPipelineLayout({
-    bindGroupLayouts: [bindGroupLayout]
-  }),
   compute: {
-// Bind group layout and bind group
- const bindGroupLayout = device.createBindGroupLayout({
-   entries: [
-     {
-       binding: 0,
-       visibility: GPUShaderStage.COMPUTE,
-       buffer: {
-         type: "read-only-storage"
-       }
-     },
-     {
-       binding: 1,
-       visibility: GPUShaderStage.COMPUTE,
-       buffer: {
-         type: "read-only-storage"
-       }
-     },
-     {
-       binding: 2,
-       visibility: GPUShaderStage.COMPUTE,
-       buffer: {
-         type: "storage"
-       }
-     }
-   ]
- });
+// Bind group
  const bindGroup = device.createBindGroup({
-  layout: bindGroupLayout,
+  layout: computePipeline.getBindGroupLayout(0 /* index */),
   entries: [

Leistungsergebnisse

Wie verhält sich die Matrixmultiplikation auf einer GPU im Vergleich zur Ausführung auf einer GPU? CPU? Um dies herauszufinden, habe ich das gerade beschriebene Programm für eine CPU geschrieben. Und wie Sie Wie Sie im Diagramm unten sehen, erscheint es naheliegend, die volle GPU-Leistung zu nutzen. wenn die Größe der Matrizen größer als 256 mal 256 ist.

<ph type="x-smartling-placeholder">
</ph> GPU vs. CPU-Benchmark
GPU- und CPU-Benchmark

Dieser Artikel war erst der Anfang meiner Reise zum Erlernen von WebGPU. Mehr erwartet Artikel zu GPU-Computing und zum Rendern (Canvas, Textur, Sampler) funktioniert mit WebGPU.