#️⃣

JavaScriptでプライベートを使うと遅くなる話

2022/02/27に公開

はじめに

class A {
  #value;
  #tryAssign(value) {
    if(Number.isInteger(value)) {
      this.#value = value;
    }
  }
  constructor(value) {
    this.#tryAssign(value);
  }
}

このソースコードは、ES2022 で追加された private field と private method を使っています。
今までは最初に_をつけてプライベートだと示していましたが、強制力はありませんでした。
しかし、今では#をつけることでプライベートにし、他からのアクセスを禁止できます。
では、#でプライベート化した物と、普通の物ではパフォーマンスに違いがあるのでしょうか。

要約

現在の結論としては、プライベートを使用した場合、パフォーマンスは落ちます。
以下の表が、この記事のまとめです。

field method time
🔴private 🔴private 248.98 ms
🔴private 🔵public 142.67 ms
🔵public 🔴private 279.88 ms
🔵public 🔵public 164.96 ms
🔶property 🔴private 139.33 ms
🔶property 🔵public 23.33 ms

計測方法を飛ばして、結論だけを見たい場合はこちら

説明

今回は、new をしたときにかかる時間を計測し、比較をしました。

function newPerformance(item, title) {
  const start = performance.now();
  for (let j = 0; j < 1000000; j++) {
    const box = new item(j);
  }
  const end = performance.now();
  console.log(`${title}: ${end - start}`);
}

この関数は、new を 100 万回してかかった時間を出力します。
この関数を使って、パフォーマンスを計測していきます。

計測環境

  • OS: Windows10 64bit
  • Node.js: v14.18.0
  • メモリ: 24.0 GB DDR3 1600 MHz
  • CPU: Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz
  • GPU: NVIDIA GeForce GTX 1050 Ti

フィールドもメソッドもプライベート

class A {
  #value;
  #tryAssign(value) {
    if (Number.isInteger(value)) {
      this.#value = value;
    }
  }
  constructor(value) {
    this.#tryAssign(value);
  }
}

newPerformance(A, 'A');

最初に見せたソースコードと一緒です。
フィールドとメソッドどちらもプライベートにしています。
計測結果は、248.98ms でした。

フィールドはプライベート。メソッドはパブリック

class B {
  #value;
  tryAssign(value) {
    if (Number.isInteger(value)) {
      this.#value = value;
    }
  }
  constructor(value) {
    this.tryAssign(value);
  }
}

newPerformance(B, 'B');

次は、フィールドのみプライベートで、メソッドはパブリックのまま実行しました。
正直言って、ほとんど変わらないと思っていましたが、
計測結果は、142.67ms でした。

早速、プライベートメソッドが速いのか、遅いのかがわかりました。
パブリックメソッドと比べておよそ 2 倍ほど遅いようです。

フィールドはパブリック。メソッドはプライベート

class C {
  value;
  #tryAssign(value) {
    if (Number.isInteger(value)) {
      this.value = value;
    }
  }
  constructor(value) {
    this.#tryAssign(value);
  }
}

newPerformance(C, 'C');

フィールドはそのままパブリックで、メソッドはプライベートにしました。
メソッドがプライベートだと 2 倍ほど遅くなるのでこちらも同じかと思いましたが…
計測結果は、279.88ms でした。

どうやら、フィールドはプライベート、メソッドなどは関係ないようです。
というか、プライベートのほうが若干早いです。

フィールドもメソッドもパブリック

class D {
  value;
  tryAssign(value) {
    if (Number.isInteger(value)) {
      this.value = value;
    }
  }
  constructor(value) {
    this.tryAssign(value);
  }
}

newPerformance(D, 'D');

あんまり変わらないと予想できますが、一応どちらもパブリックで計測しました。
計測結果は、164.96ms でした。
予想通り、メソッドのみパブリックの結果とほぼ同じか少し遅いぐらいでした。

つまり、最もパフォーマンスがいいのは、メソッドのみパブリックということでいいでしょうか。

しかし

class Zero {}

newPerformance(Zero, 'None');

この、何にもないクラスで測定してみます。
計測結果は、19.93ms でした。
何もない状態からメソッドとフィールドを 1 つ追加するだけで、これだけ時間が変わってくるのでしょうか。
疑問に思ったのでさらに早くなるか確かめてみました。
すると、原因はフィールドということがわかり、インスタンスプロパティなら速くなることがわかりました。

メソッドはプライベート。インスタンスプロパティに変更

class E {
  #tryAssign(value) {
    if (Number.isInteger(value)) {
      this.value = value;
    }
  }
  constructor(value) {
    this.#tryAssign(value);
  }
}

newPerformance(E, 'E');

比較をするため、まずはメソッドをプライベートのまま、フィールドをインスタンスプロパティに変更して計測しました。
計測結果は、139.33ms でした。

248.98msと比べると、二倍ほどパフォーマンスは上げることに成功しました。

メソッドはパブリック。インスタンスプロパティに変更

class F {
  tryAssign(value) {
    if (Number.isInteger(value)) {
      this.value = value;
    }
  }
  constructor(value) {
    this.tryAssign(value);
  }
}

ということで、最後は現状の最速同士を組み合わせ。メソッドはパブリックで、フィールドはインスタンスプロパティに変更して、計測してみました。
計測結果は、23.33ms でした。

最初のプライベートを使ったクラスよりも 10 倍ほどパフォーマンスを向上することに成功しました。

結論

field method time
🔴private 🔴private 248.98 ms
🔴private 🔵public 142.67 ms
🔵public 🔴private 279.88 ms
🔵public 🔵public 164.96 ms
🔶property 🔴private 139.33 ms
🔶property 🔵public 23.33 ms

現在のところ、プライベートクラス機能を使うと遅くなります。
プライベートメソッドは、new にかかる時間が従来のメソッドより 100ms ほど増加します。
また、プライベートフィールド(普通のフィールドも含む)を使った場合もインスタンスプロパティより 100ms ほど増加します。
そのため、従来通りでクラスを作った場合と比べ、200ms ほどのパフォーマンスの違いが生まれます。

しかし、この機能は ES2022 で追加された新しい機能。
これからパフォーマンスがよくなる可能性はあります。
また、パフォーマンスを気にすることがなければ全然使って大丈夫です。
もし、パフォーマンスを気にしないといけないのであれば、従来の _ を使うのがいいでしょう。

終わりに

人生で初めて記事を書いたので、どこかに訂正等あるはずです。
何かあった場合は、コメントお願いします。

Discussion