# When the CPU Wins

*Task 4 of 4 · [Measuring Speed Honestly](https://gpu.rocks/learn/measuring-speed-honestly-b9188894.md) · GPU.js Learn*

Sixteen numbers, doubled. The GPU *can* do it — but every kernel call pays
a fixed toll before any math happens: dispatch through the graphics API, upload 16 values,
read 16 back. A plain JavaScript loop finishes the whole job in nanoseconds, before the
GPU has cleared its throat.

This is the module's payoff — the full honest-measurement checklist in one run:
**warm up first** (task 1), **remember the transfer toll**
(task 2), **compare results with a tolerance** (task 3), and then
**declare the true winner** — even when it isn't the GPU. Parallel hardware
pays off on big workloads; on tiny ones, the honest answer is a for-loop.

## Goal

**Goal:** double `tiny` both ways — kernel and plain loop —
verify they agree within a tolerance, time both fairly, and log the winner.

## Requirements

- Kernel returns `data[this.thread.x] * 2` for all 16 threads
- Compare `fromKernel` to `fromLoop` element-wise with tolerance `1e-4` and log `match: true`
- Time 200 warmed-up rounds of each contender and log both as `ms/round`
- Log `winner:` with whichever contender was faster

## Hint 1 — the tolerant match

Task 3's move, in a loop: start with `let allMatch = true;` and flip
it to `false` whenever `Math.abs(fromKernel[i] - fromLoop[i]) > 1e-4`.

## Hint 2 — a fair fight

The first `doubleTiny(tiny)` call already warmed the kernel up, so
both timed loops measure steady state. Time 200 rounds of `doubleTiny(tiny)`,
then 200 rounds of the JS loop, and divide each total by 200.

## Hint 3 — declaring the winner

```js
console.log('winner:', kernelMs < loopMs ? 'gpu kernel' : 'plain js');
```

On a job this small, expect the loop to take it. That's the honest answer.

## Same idea elsewhere

Kernel-launch overhead runs to microseconds on CUDA and ROCm — thousands of
CPU instructions' worth per launch. It's why serious frameworks batch and fuse tiny
operations instead of dispatching them one at a time, and why "is this workload big
enough?" is the first question asked in any GPU port.

## Starter code

```js
// 16 numbers. The GPU CAN double them — but should it?
const gpu = new GPU({ mode });

const doubleTiny = gpu.createKernel(function (data) {
  // TODO: return double this thread's element
  return data[this.thread.x];
}, { output: [16] });

const fromKernel = await doubleTiny(tiny); // also serves as the warm-up call

// The same job, plain JavaScript:
const fromLoop = new Array(16);
for (let i = 0; i < 16; i++) fromLoop[i] = tiny[i] * 2;

// TODO: compare fromKernel and fromLoop element-wise with tolerance 1e-4
// (task 3!) and log:  console.log('match:', allMatch);

// TODO: time 200 rounds of each contender with Date.now(), then log:
//   console.log('kernel:  ', kernelMs, 'ms/round');
//   console.log('plain js:', loopMs, 'ms/round');
//   console.log('winner:', kernelMs < loopMs ? 'gpu kernel' : 'plain js');
```

---

Interactive version: https://gpu.rocks/learn/measuring-speed-honestly-b9188894/4

[Previous task](https://gpu.rocks/learn/measuring-speed-honestly-b9188894/3.md)
