🎲

Node16, 18 では arr.at(-1) は arr[arr.length-1] より遅い

2023/03/26に公開

OSSやってて教えてもらったんですが、Node.js 16 から使えるようになっている Array.prototype.at は、実は Node16, Node18 では通常のプロパティアクセスに比べて大幅に遅いらしいです。

実際に計測してみました。

計測スクリプトはこちらです。ちょっと長いですが、array[array.length - 1]array.at(-1) をそれぞれ 1e8 回実行するのにかかった時間を PerformanceObserver で計測しているだけです。(リポジトリは https://github.com/sosukesuzuki/array-prototype-at-perf/tree/main )

const { PerformanceObserver, performance } = require("node:perf_hooks");

const marks = {
  propertyAccess: {
    start: "START_PROPERTY_ACCESS",
    end: "END_PROPERTY_ACCESS",
  },
  arrayPrototypeAt: {
    start: "START_ARRAY_PROTOTYPE_AT",
    end: "END_ARRAY_PROTOTYPE_AT",
  },
};

const observer = new PerformanceObserver((items) => {
  const perfEntries = items
    .getEntries()
    .sort((a, b) => (a.name.length < b.name.length ? 1 : -1));
  for (const perfEntry of perfEntries) {
    const targetLength = perfEntries[0].name.length;
    console.log(
      perfEntry.name.padEnd(targetLength, " "),
      `${perfEntry.duration} ms`
    );
  }
  performance.clearMarks();
});

observer.observe({ type: "measure" });

const array = [1, 2, 3];

performance.mark(marks.propertyAccess.start);
for (let i = 0; i < 1e8; i++) {
  array[array.length - 1];
}
performance.mark(marks.propertyAccess.end);

performance.measure(
  "array[array.length -1]",
  marks.propertyAccess.start,
  marks.propertyAccess.end
);

performance.mark(marks.arrayPrototypeAt.start);
for (let i = 0; i < 1e8; i++) {
  array.at(-1);
}
performance.mark(marks.arrayPrototypeAt.end);

performance.measure(
  "array.at(-1)",
  marks.arrayPrototypeAt.start,
  marks.arrayPrototypeAt.end
);

このスクリプトを Node.js 16, Node.js 18, Node.js 19 でそれぞれ実行した結果が以下です。

Node.js 16:

array[array.length -1] 47.548583984375 ms
array.at(-1)           2733.349083006382 ms

Node.js 18:

array[array.length -1] 47.59833401441574 ms
array.at(-1)           2463.5460420250893 ms

Node.js 19:

array[array.length -1] 47.45416694879532 ms
array.at(-1)           62.53858298063278 ms

スーパー遅い!こんなに遅いなら、Node.js 16, 18 ではまだ Array.prototype.at は使わない方がよいかもしれませんね。

Node.js 18 の V8 が 10.1 で Node.js 19 の V8 が 10.7 らしいので、その間の V8 でなにかしらの最適化が入ったのだと思うんですが、私のV8力では該当コミットを見つけることができませんでした。知っている人は教えてほしいです。

Discussion