JavaScriptで渡された配列引数をシャローコピーする場合デフォルト引数を適当に使うより引数省略を検知してシャローコピーを省略したほうが高速

とあるpull requestで. JavaScriptクラスのコンストラクタで. 配列引数にundefinedが渡されていることをtypeof foo === 'undefined'によって検知して. 引数が省略されている場合new Arrayして, 引数が省略されていなかった場合配列のシャローコピーを行っているのを見ました.

私は見た, 思った, 書いた. 「それデフォルト引数設定すれば良くないですか?」

とすると「引数を省略した場合配列のコピーが生じるのでかなりパフォーマンスが悪くなる」と返されました.

私はそんなことで大した差が生じるとは思えなかったので, とりあえず計測するコードを書きました.

JavaScriptのベンチマークツールもっと良いのが知りたいです.

const Benchmark = require("benchmark");

class DefaultArg {
  constructor(foo = new Array(19 * 19).fill(0)) {
    this.bar = [...foo];
  }
}

class IfSet {
  constructor(foo) {
    if (typeof foo === "undefined") {
      foo = new Array(19 * 19);
      foo.fill(0);
      this.bar = foo;
    } else {
      this.bar = [...foo];
    }
  }
}

const suite = new Benchmark.Suite();

// add tests
suite
  .add("DefaultArg", () => {
    new DefaultArg();
  })
  .add("IfSet", () => {
    new IfSet();
  })
  .on("cycle", function (event) {
    console.log(String(event.target));
  })
  .on("complete", function () {
    console.log("Fastest is " + this.filter("fastest").map("name"));
  })
  .run();
2018-03-02T17:23:45 ncaq@karen/pts/7(0) ~/Documents/archive/2018-03
% node default-arg-vs-if-set.js
DefaultArg x 62,730 ops/sec ±0.74% (92 runs sampled)
IfSet x 1,443,601 ops/sec ±0.69% (90 runs sampled)
Fastest is IfSet

予想に反して手動検知するほうがかなり速いことがわかりました.

V8の最適化を過信しすぎていたようですね. あとパフォーマンスの肌感覚が身についていなかった.

まあ私が書く時は今後もデフォルト引数を適当に使っていきますが, 気にする人にも正当性があることがわかりました.

追記: 最近のNode.jsで検証

結構前に「これ昔の話だし最近のNodeだとどうなんですか」って聞かれて測ってみたのですが、その時測ったら前の計測よりどちらとも遅くなっていました。 CPUが違うのとかLTO切れたのが原因なのか、だとしたら正確な計測は出来ないなと思って放置していたのですが、またマシンを変えたので計測し直してみました。

  • 年月: 2023-01-29
  • node -v: v18.13.0
  • uname -a: Linux bullet 6.1.8-gentoo #1 SMP PREEMPT_DYNAMIC Sat Jan 28 05:42:00 JST 2023 x86_64 AMD Ryzen 9 7950X 16-Core Processor AuthenticAMD GNU/Linux
~/Documents/archive/2018-03 on  master [?] via  v18.13.0 took 10s
2023-01-29T21:11:54 ❯ node default-arg-vs-if-set.js
DefaultArg x 1,632,057 ops/sec ±0.72% (86 runs sampled)
IfSet x 4,024,161 ops/sec ±0.03% (103 runs sampled)
Fastest is IfSet

どちらもCPUかNodeの性能向上のおかげか、昔性能検査した時より大幅に早くなっていることが分かりました。

しかし結局分岐して手動で入れた方が早いということには変わりませんでした。

JITが関係しているのかもしれないと思って計測順を入れ替えてみました。

2023-01-29T21:22:00 ❯ node default-arg-vs-if-set.js
IfSet x 3,932,261 ops/sec ±0.28% (96 runs sampled)
DefaultArg x 1,822,344 ops/sec ±1.69% (87 runs sampled)
Fastest is IfSet

多少差は縮まりましたが、やはり手動の方が早いということには変わリませんでした。