Skip to content

Instantly share code, notes, and snippets.

@uhop
Last active September 9, 2026 20:11
Show Gist options
  • Select an option

  • Save uhop/97773df567141930eef9bc7caada915b to your computer and use it in GitHub Desktop.

Select an option

Save uhop/97773df567141930eef9bc7caada915b to your computer and use it in GitHub Desktop.
Duff's device in JavaScript — benchmark from lazutkin.com. Run: npm install && npm run bench

Copying one array into another: hand-written loops and Duff's device

Benchmark from lazutkin.com, the companion to Duff's device in JavaScript.

The follow-up, Duff's device, part 2: copying within an array, asks the same question of a range copied inside one array, where Array#copyWithin competes; its benchmark is in a separate gist.

The files

  • bench-duff.mjs — the four contenders, all copying 127 elements from one array into another: the simple loop; unrolled-loop, unrolled by ten with a second loop for the remainder; duff-device, unrolled by ten with a fall-through switch for the remainder; and duff-device-nested, one loop stepping by ten where a switch on the remaining count decides how much of the block runs.

Running it

npm install
npm run bench

The harness is nano-bench; its Concepts page explains how the confidence intervals and the significance tests are computed. Defaults are 100 samples of 50 ms per function.

The work is CPU-bound, so run one process at a time and one machine at a time. Results move with the engine, the engine version and the CPU, so a number from a single box says nothing on its own.

const SIZE = 127;
export const source = new Array(SIZE);
export const target = new Array(source.length);
for (let i = 0; i < source.length; ++i) {
source[i] = i;
}
export default {
'simple-loop': n => {
for (let i = 0; i < n; ++i) {
for (let j = 0; j < source.length; ++j) {
target[j] = source[j];
}
}
},
'unrolled-loop': n => {
for (let i = 0; i < n; ++i) {
let j = 0;
for (; j + 10 <= source.length; j += 10) {
target[j] = source[j];
target[j + 1] = source[j + 1];
target[j + 2] = source[j + 2];
target[j + 3] = source[j + 3];
target[j + 4] = source[j + 4];
target[j + 5] = source[j + 5];
target[j + 6] = source[j + 6];
target[j + 7] = source[j + 7];
target[j + 8] = source[j + 8];
target[j + 9] = source[j + 9];
}
for (; j < source.length; ++j) {
target[j] = source[j];
}
}
},
'duff-device': n => {
for (let i = 0; i < n; ++i) {
let j = 0;
for (; j + 10 <= source.length; j += 10) {
target[j] = source[j];
target[j + 1] = source[j + 1];
target[j + 2] = source[j + 2];
target[j + 3] = source[j + 3];
target[j + 4] = source[j + 4];
target[j + 5] = source[j + 5];
target[j + 6] = source[j + 6];
target[j + 7] = source[j + 7];
target[j + 8] = source[j + 8];
target[j + 9] = source[j + 9];
}
switch (source.length - j) {
case 9: target[j + 8] = source[j + 8];
case 8: target[j + 7] = source[j + 7];
case 7: target[j + 6] = source[j + 6];
case 6: target[j + 5] = source[j + 5];
case 5: target[j + 4] = source[j + 4];
case 4: target[j + 3] = source[j + 3];
case 3: target[j + 2] = source[j + 2];
case 2: target[j + 1] = source[j + 1];
case 1: target[j] = source[j];
}
}
},
'duff-device-nested': n => {
for (let i = 0; i < n; ++i) {
for (let j = 0; j < source.length; j += 10) {
switch (Math.min(source.length - j, 10)) {
case 10: target[j + 9] = source[j + 9];
case 9: target[j + 8] = source[j + 8];
case 8: target[j + 7] = source[j + 7];
case 7: target[j + 6] = source[j + 6];
case 6: target[j + 5] = source[j + 5];
case 5: target[j + 4] = source[j + 4];
case 4: target[j + 3] = source[j + 3];
case 3: target[j + 2] = source[j + 2];
case 2: target[j + 1] = source[j + 1];
case 1: target[j] = source[j];
}
}
}
}
};
{
"name": "duff-device-bench",
"private": true,
"type": "module",
"scripts": {
"bench": "nano-bench bench-duff.mjs"
},
"devDependencies": {
"nano-benchmark": "^1.2.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment