JavaScript でループ展開しますか?

王林
リリース: 2024-07-24 13:18:52
オリジナル
951 人が閲覧しました

Loop Unrolling in JavaScript?

JavaScript は、それが実行されるハードウェアから非常に切り離されているように感じられることがありますが、低レベルで考えることは限られた場合には依然として役立ちます。

ループの最適化に関する Kafeel Ahmad の最近の投稿では、多数のループ パフォーマンス向上テクニックについて詳しく説明しています。その記事を読んで、このトピックについて考えるようになりました。

時期尚早な最適化

これを邪魔にならないようにしておきますが、これは Web 開発で考慮する必要があるテクニックはほとんどありません。また、最適化に集中するのが早すぎると、コードの作成が困難になり、保守がさらに困難になる可能性があります。低レベルのテクニックを覗いてみると、たとえその知識を直接適用できなくても、ツールや作業全般について洞察を得ることができます。

ループアンローリングとは何ですか?

ループ展開は基本的にループ内のロジックを複製するため、ループごとに複数の操作を実行します。特定のケースでは、ループ内のコードを 長くすると、高速化することができます。

一部の操作を 1 つずつではなく、意図的に グループ で実行することにより、コンピューターはより効率的に動作できる可能性があります。

展開例

非常に簡単な例を見てみましょう: 配列内の値を合計します。

// 1-to-1 looping
const simpleSum = (data) => {
  let sum = 0;
  for(let i=0; i < data.length; i += 1) {
    sum += data[i];
  }
  return sum;
};

const parallelSum = (data) => {
  let sum1 = 0;
  let sum2 = 0;
  for(let i=0; i < data.length; i += 2) {
    sum1 += data[i];
    sum2 += data[i + 1];
  }
  return sum1 + sum2;
};
ログイン後にコピー

これは最初は非常に奇妙に見えるかもしれません。より多くの変数を管理し、単純な例では発生しない追加の操作を実行しています。これをどうすれば速くできるでしょうか?!

違いを測る

さまざまなデータ サイズと複数回の実行でいくつかの比較を実行したほか、シーケンシャル テストまたはインターリーブ テストも実行しました。 ParallelSum のパフォーマンスにはばらつきがありましたが、非常に小さいデータ サイズでの奇妙な結果を除いて、ほとんどの場合、パフォーマンスが向上しました。 Chrome の V8 エンジン上に構築された RunJS を使用してこれをテストしました。

さまざまなデータ サイズにより、非常に大まかに次のような結果が得られます:

  • 小さい (<10,000): ほとんど違いはありません
  • 中 (10k-100k): 通常、~20-80% 高速
  • 大 (> 1M): 一貫して 2 倍の速度

次に、100 万件のレコードを含む JSPerf を作成し、さまざまなブラウザーで試してみました。ぜひ試してみてください!

RunJS テストから予想されたとおり、Chrome は、ParallelSum を simpleSum の 2 倍の速度で実行しました。

Safari は、パーセントと 1 秒あたりの操作数の両方において Chrome とほぼ同じでした。

同じシステム上の Firefox は、simpleSum ではほぼ同じパフォーマンスを示しましたが、ParallelSum は 2 倍ではなく、わずか約 15% 高速でした。

このバリエーションを参照して、さらに詳しい情報を探しました。決定的なものではありませんが、ループ展開に関する JS エンジンの問題の一部について論じた 2016 年の StackOverflow コメントを見つけました。これは、エンジンと最適化が予期しない形でコードにどのような影響を与える可能性があるかを示す興味深い考察です。

バリエーション

3 番目のバージョンも試してみました。これは 1 回の操作で 2 つの値を追加し、1 つの変数と 2 つの変数の間に顕著な違いがあるかどうかを確認しました。

const parallelSum = (data) => {
  let sum = 0
  for(let i=0; i < data.length; i += 2) {
    sum += data[i] + data[i + 1];
  }
  return sum;
};
ログイン後にコピー

短い答え: いいえ。2 つの「並列」バージョンは、報告されている互いの誤差範囲内にありました。

では、どのように機能するのでしょうか?

JavaScript はシングルスレッドですが、特定の条件が満たされた場合、その下のインタープリター、コンパイラー、およびハードウェアが最適化を実行できます。

簡単な例では、操作にはどのデータをフェッチするかを知るために値 i が必要で、更新するには sum の最新の値が必要です。これらは両方ともループごとに変化するため、コンピューターはさらにデータを取得するためにループが完了するまで待つ必要があります。私たちにとって i += 1 が何をするかは明白に思えるかもしれませんが、コンピューターは「値が変更されるので、後で確認してください」ということをほとんど理解しているため、最適化するのが困難です。

私たちの並列バージョンでは、i の値ごとに複数のデータ エントリがロードされます。依然として各ループの合計に依存していますが、サイクルごとに 2 倍の量のデータをロードして処理できます。ただし、2 倍の速度が実行されるという意味ではありません。

より深いダイブ

なぜループ展開が機能するのかを理解するために、コンピューターの低レベルの操作に注目します。スーパースカラ アーキテクチャのプロセッサは、複数のパイプラインを備えて同時操作を実行できます。これらはアウトオブオーダー実行をサポートできるため、相互に依存しない操作をできるだけ早く実行できます。一部の操作では、SIMD は複数のデータに対して 1 つのアクションを同時に実行できます。さらに、キャッシュ、データのフェッチ、分岐予測を始めます...

しかし、これは JavaScript の記事です。そこまで深くはいきません。プロセッサ アーキテクチャについてさらに詳しく知りたい場合は、Anandtech に優れた詳細情報がいくつかあります。

制限と欠点

ループの展開は魔法ではありません。プログラムやデータのサイズ、操作の複雑さ、コンピューターのアーキテクチャなどにより、制限や利益逓減が生じます。ただし、テストしたのは 1 つまたは 2 つの操作だけであり、最近のコンピューターは 4 つ以上のスレッドをサポートしていることがよくあります。

さらに大きな増分を試すために、1、2、4、10 レコードで別の JSPerf を作成し、macOS 14.5 Sonoma を実行する Apple M1 Max MacBook Pro と Windows 11 を実行する AMD Ryzen 9 3950X PC で実行しました。

一度に 10 件のレコードを処理すると、基本ループよりも 2.5 ~ 3.5 倍高速になりましたが、Mac で 4 つのレコードを処理するよりも 12 ~ 15% 速くなっただけです。 PC では、1 ~ 2 レコードの間では 2 倍の向上が見られましたが、10 レコードは 4 レコードよりもわずか 2% 高速であり、これは 16 コア プロセッサーでは予測できなかったでしょう。

プラットフォームとアップデート

これらのさまざまな結果は、最適化には注意する必要があることを思い出させます。お使いのコンピューターに合わせて最適化すると、機能の低いハードウェアや異なるハードウェアではエクスペリエンスが悪化する可能性があります。古いハードウェアまたはエントリーレベルのハードウェアのパフォーマンスや機能の問題は、開発者が高速で強力なマシンを扱うときによくある問題であり、私もこれまでのキャリアの中で何度もこの課題に取り組んできました。

HP から現在入手可能なエントリーレベルの Chromebook には、ある程度のパフォーマンスの点で Intel Celeron N4120 プロセッサが搭載されています。これは、私の 2013 Core i5-4250U MacBook Air とほぼ同等です。合成ベンチマークでは、M1 Max の9 分の 1 のパフォーマンスしかありません。最新バージョンの Chrome を実行している 2013 MacBook Air では、4 レコード機能は 10 レコードよりも高速でしたが、それでも単一レコード機能よりもわずか 60% 高速でした。

ブラウザと標準も常に変化しています。定期的にブラウザを更新したり、プロセッサ アーキテクチャが異なると、最適化されたコードが通常のループよりも遅くなる可能性があります。徹底的に最適化していることに気づいた場合は、その最適化が消費者にとって適切であること、そして関連性を維持していることを確認する必要があるかもしれません。

2012 年に読んだ、ニコラス・ザカス著『High Performance JavaScript』という本を思い出します。この本は素晴らしい本で、多くの洞察が含まれていました。しかし、2014 年までに、この本で特定された重大なパフォーマンスの問題の多くはブラウザ エンジンの更新によって解決されるか、大幅に軽減され、保守可能なコードの作成により多くの労力を集中できるようになりました。

常にパフォーマンスを最適化したい場合は、変更と定期的な検証に備えてください。

過去からの教訓

このトピックを調査しているときに、最終的にアプリケーションのパフォーマンスを向上させるループ アンローリングの最適化の削除に関する 2000 年の Linux カーネル メーリング リストのスレッドを見つけました。これには、現在でも関連する次の点が含まれていました (私の点を強調):

肝心なのは、何が高速で何が高速でないかについての直感的な仮定は、多くの場合間違っている可能性があるということです、特にここ数年で CPU がどれだけ変化したかを考えると、
– セオドア・ツォ

結論

ループからパフォーマンスを絞り出す必要がある場合があります。十分な項目を処理している場合、これはその方法の 1 つになる可能性があります。この種の最適化について知っておくことは良いことですが、ほとんどの作業では、You Aren't Gonna Need It™ が必要です。

それでも、私のとりとめのない話を楽しんでいただければ幸いです。おそらく将来、パフォーマンスの最適化に関する考慮事項について記憶が呼び覚まされることでしょう。

読んでいただきありがとうございます!

以上がJavaScript でループ展開しますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート