私たちの 天体物理学シミュレーター 希望、誇大宣伝、そして新しい計算能力へのアクセスの強力なロケット燃料混合物によって動力を与えられています。
この計算能力にアクセスするには Webワーカー 。すでにWebワーカーに精通している場合は、 コード 次の記事で説明するWebAssemblyにジャンプします。
JavaScriptは、静的Webにいくつかの非常に便利な機能をもたらしたため、最もインストールされ、学習され、アクセスしやすいプログラミング言語になりました。
シングルスレッド つまり、マルチスレッドプログラミングの複雑さと落とし穴についてあまり心配する必要はありません。
非同期 つまり、後で実行するパラメーターとして、つまりイベントループ内のイベントとして関数を渡すことができます。
これらの機能とChromeのパフォーマンスへのGoogleの巨額の投資 V8JavaScriptエンジン 、優れた開発者ツールとともに、JavaScriptとNode.jsはマイクロサービスアーキテクチャに最適です。
シングルスレッド実行は、スパイウェアが蔓延しているすべてのブラウザタブランタイムをコンピュータの複数のコア間で安全に分離して実行する必要があるブラウザメーカーにも最適です。
質問: 1つのブラウザタブですべてのコンピュータのCPUコアにアクセスするにはどうすればよいですか?
回答: Webワーカー!
Webワーカーは、イベントループを使用して、スレッド間でメッセージを非同期に渡し、マルチスレッドプログラミングの潜在的な落とし穴の多くを回避します。
Webワーカーを使用して、メインUIスレッドから計算を移動することもできます。これにより、メインUIスレッドがクリック、アニメーション、およびDOMの管理を処理できるようになります。
からのいくつかのコードを見てみましょう プロジェクトのGitHubリポジトリ 。
アーキテクチャ図を覚えている場合は、シミュレーション全体をnBodySimulator
に委任しました。したがって、Webワーカーを管理します。
あなたがから覚えているなら イントロポスト 、nBodySimulator
step()
がありますシミュレーションの33msごとに呼び出される関数。 calculateForces()
を呼び出してから、位置を更新して再描画します。
// Methods from class nBodySimulator /** * The simulation loop */ start() { // This is the simulation loop. step() calls visualize() const step = this.step.bind(this) setInterval(step, this.simulationSpeed) } /** * A step in the simulation loop. */ async step() { // Skip calculation if worker not ready. Runs every 33ms (30fps), expect it to skip. if (this.ready()) { await this.calculateForces() } else { console.log(`Skipping calculation: WorkerReady: ${this.workerReady} WorkerCalculating: ${this.workerCalculating}`) } // Remove any 'debris' that has traveled out of bounds - this is for the button this.trimDebris() // Now Update forces. Reuse old forces if we skipped calculateForces() above this.applyForces() // Ta-dah! this.visualize() }
Web Workerの貢献は、WebAssembly用に別のスレッドをホストすることです。低水準言語として、WebAssemblyは整数と浮動小数点数のみを理解します。 JavaScriptを渡すことはできません 文字列 または オブジェクト -「線形メモリ」へのポインタだけ。したがって、便宜上、「ボディ」をフロートの配列にパッケージ化します:arrBodies
。
これについては、WebAssemblyとAssemblyScriptに関する記事で説明します。
ここでは、実行するWebワーカーを作成していますcalculateForces()
別のスレッドで。これは、ボディ(x、y、z、質量)をフロートの配列にマーシャリングするときに以下で発生しますarrBodies
、次にthis.worker.postMessage()
労働者に。ワーカーが後で解決するという約束をthis.worker.onMessage()
で返します。
// src/nBodySimulator.js /** * Use our web worker to calculate the forces to apply on our bodies. */ calculateForces() { this.workerCalculating = true this.arrBodies = [] // Copy data to array into this.arrBodies ... // return promise that worker.onmessage will fulfill const ret = new Promise((resolve, reject) => { this.forcesResolve = resolve this.forcesReject = reject }) // postMessage() to worker to start calculation // Execution continues in workerWasm.js worker.onmessage() this.worker.postMessage({ purpose: 'nBodyForces', arrBodies: this.arrBodies, }) // Return promise for completion // Promise is resolve()d in this.worker.onmessage() below. // Once resolved, execution continues in step() above - await this.calculateForces() return ret }
上から、ブラウザのGET index.html
実行されます main.js
これは、 new nBodySimulator()
そしてそのコンストラクターで私達は見つけます setupWebWorker()
。
// nBodySimulator.js /** * Our n-body system simulator */ export class nBodySimulator { constructor() { this.setupWebWorker() ...
私たちのnew nBodySimulator()
メインUIスレッドに存在し、setupWebWorker()
workerWasm.js
をフェッチしてWebワーカーを作成しますネットワークから。
// nBodySimulator.js // Main UI thread - Class nBodySimulator method setupWebWorker() { // Create a web worker (separate thread) that we'll pass the WebAssembly module to. this.worker = new Worker('workerWasm.js'); // Console errors from workerWasm.js this.worker.onerror = function (evt) { console.log(`Error from web worker: ${evt.message}`); } ...
new Worker()
で、ブラウザはworkerWasm.js
をフェッチして実行します。別のJavaScriptランタイム(およびスレッド)で、メッセージの受け渡しを開始します。
不和ボットを取得する方法
次に、workerWasm.js
WebAssemblyの要点に触れますが、実際には1つだけですthis.onmessage()
switch()
を含む関数ステートメント。
Webワーカーはネットワークにアクセスできないため、メインUIスレッドはコンパイルされたWebAssemblyコードをメッセージとしてWebワーカーに渡す必要があることに注意してくださいresolve('action packed')
。これについては、次の投稿で詳しく説明します。
// workerWasm.js - runs in a new, isolated web worker runtime (and thread) this.onmessage = function (evt) { // message from UI thread var msg = evt.data switch (msg.purpose) { // Message: Load new wasm module case 'wasmModule': // Instantiate the compiled module we were passed. ... // Tell nBodySimulation.js we are ready this.postMessage({ purpose: 'wasmReady' }) return // Message: Given array of floats describing a system of bodies (x, y, z, mass), // calculate the Grav forces to be applied to each body case 'nBodyForces': ... // Do the calculations in this web worker thread synchronously const resultRef = wasm.nBodyForces(dataRef); ... // See nBodySimulation.js’ this.worker.onmessage return this.postMessage({ purpose: 'nBodyForces', arrForces }) } }
setupWebWorker()
に戻る私たちの方法nBodySimulation
クラスでは、同じonmessage() + switch()
を使用してWebワーカーのメッセージをリッスンします。パターン。
// Continuing class nBodySimulator’s setupWebWorker() in the main UI thread // Listen for messages from workerWasm.js postMessage() const self = this this.worker.onmessage = function (evt) { if (evt && evt.data) { // Messages are dispatched by purpose const msg = evt.data switch (msg.purpose) { // Worker’s reply that it has loaded the wasm module we compiled and sent. Let the magic begin! // See postmessage at the bottom of this function. case 'wasmReady': self.workerReady = true break // wasm has computed forces for us // Response to postMessage() in nBodySimulator.calculateForces() above case 'nBodyForces': self.workerCalculating = false // Resolve await this.calculateForces() in step() above if (msg.error) { self.forcesReject(msg.error) } else { self.arrForces = msg.arrForces self.forcesResolve(self.arrForces) } break } } } ...
この例では、calculateForces()
約束を作成して返します 保存resolve()
およびreject()
としてself.forcesReject()
およびself.forcesResolve()
。
このように、worker.onmessage()
calculateForces()
で作成されたpromiseを解決できます。
シミュレーションループのstep()
を覚えているなら関数:
/** * This is the simulation loop. */ async step() { // Skip calculation if worker not ready. Runs every 33ms (30fps), expect it to skip. if (this.ready()) { await this.calculateForces() } else { console.log(`Skipping calculation: WorkerReady: ${this.workerReady} WorkerCalculating: ${this.workerCalculating}`) }
これにより、calculateForces()
をスキップできますWebAssemblyがまだ計算中の場合は、前のフォースを再適用します。
このステップ関数は33msごとに起動します。 Webワーカーの準備ができていない場合は、以前の力を適用してペイントします。特定のステップの場合calculateForces()
次のステップの開始を過ぎて動作する場合、次のステップは前のステップの位置から力を適用します。これらの以前の力は、「正しく」見えるように十分に類似しているか、ユーザーが理解できないほど速く発生しています。このトレードオフにより、実際の人間の宇宙旅行には推奨されない場合でも、知覚されるパフォーマンスが向上します。
これは改善できますか?はい! setInterval
の代替私たちのステップ関数は requestAnimationFrame()
。
私の目的では、これはCanvas、WebVR、およびWebAssemblyを探索するのに十分です。何かが追加または交換される可能性があると思われる場合は、コメントするか、連絡してください。
w2時給vs給与計算機
最新の完全な物理エンジンの設計をお探しの場合は、オープンソースをご覧ください Matter.js 。
WebAssemblyは、ブラウザやシステム間で機能するポータブルバイナリです。 WebAssemblyは、多くの言語(C / C ++ / Rustなど)からコンパイルできます。私自身の目的のために、AssemblyScriptを試してみたかったのです。これは、JavaScriptに基づく言語であるTypeScriptに基づく言語です。 カメはずっと下に 。
AssemblyScriptはコンパイルします TypeScriptコード ポータブルに 「オブジェクトコード」バイナリ 、Wasmと呼ばれる新しい高性能ランタイムに「ジャストインタイム」でコンパイルされます。 TypeScriptを.wasm
にコンパイルする場合バイナリ、.wat
を作成することが可能です人間が読める形式 「WebAssemblyテキスト」形式 バイナリを記述します。
setupWebWorker()
の最後の部分WebAssemblyに関する次の投稿を開始し、ネットワークへのアクセスに関するWebワーカーの制限を克服する方法を示します。私たちfetch()
wasm
メインUIスレッドでファイルを作成し、「ジャストインタイム」でネイティブのwasmモジュールにコンパイルします。私たちpostMessage()
Webワーカーへのメッセージとしてのそのモジュール:
// completing setupWebWorker() in the main UI thread … // Fetch and compile the wasm module because web workers cannot fetch() WebAssembly.compileStreaming(fetch('assembly/nBodyForces.wasm')) // Send the compiled wasm module to the worker as a message .then(wasmModule => { self.worker.postMessage({ purpose: 'wasmModule', wasmModule }) }); } }
workerWasm.js
次に、そのモジュールをインスタンス化して、その関数を呼び出すことができるようにします。
// wasmWorker.js - web worker onmessage function this.onmessage = function (evt) { // message from UI thread var msg = evt.data switch (msg.purpose) { // Message: Load new wasm module case 'wasmModule': // Instantiate the compiled module we were passed. wasm = loader.instantiate(msg.wasmModule, importObj) // Throws // Tell nBodySimulation.js we are ready this.postMessage({ purpose: 'wasmReady' }) return case 'nBodyForces': ... // Do the calculations in this thread synchronously const resultRef = wasm.nBodyForces(dataRef);
これがWebAssembly機能にアクセスする方法です。未編集のソースコードを見ると、...
に気付くでしょう。データをdataRef
に取り込むための一連のメモリ管理コードです。そしてresultRef
からの結果。 JavaScriptでのメモリ管理?エキサイティング!
次の投稿では、WebAssemblyとAssemblyScriptについて詳しく説明します。
ここで話すべきことが他にあります。それは実行境界と共有メモリです。
WebAssemblyの記事は非常に戦術的であるため、ここではランタイムについて説明するのに適した場所です。 JavaScriptとWebAssemblyは「エミュレートされた」ランタイムです。実装されているように、実行時の境界を超えるたびに、ボディデータ(x、y、z、mass)のコピーを作成しています。メモリのコピーは安価ですが、これは成熟した高性能設計ではありません。
幸いなことに、多くの非常に賢い人々が、これらの最先端のブラウザー技術の仕様と実装の作成に取り組んでいます。
JavaScriptには SharedArrayBuffer 呼び出し時に(2)->(3)からpostMessage()
のコピーとonmessage()
のarrForces
のコピーを削除する共有メモリオブジェクトを作成します。結果の(3)->(2)から。
WebAssemblyには 線形メモリ nBodyForces()
の共有メモリをホストできる設計(3)->(4)から呼び出します。 Webワーカーは、結果配列の共有メモリを渡すこともできます。
次回は、JavaScriptメモリ管理へのエキサイティングな旅にご参加ください。
エッジコンピューティングは、新しいデバイスをレンタルする前に、アプリを実行しているすべてのエッジデバイスの計算能力を使用するという概念です。
Webワーカーは、バックグラウンドで独立して実行されるJavaScriptコードの一部であるため、ページのパフォーマンスに影響を与えません。
ウィンドウとWebワーカーのonMessage()関数とpostMessage()関数を使用する。
データは、整数、浮動小数点数、または線形メモリへのインデックスである必要があります。