複数のファイルを読み込むことが増えてきたので、ファイル読み込みの効率化について考えてみました。サーバーや回線が良ければ、複数同時に読み込み要求を出せばよいのですが、実際は複数ユーザーの同時アクセスや回線の制限があるため無制限にというわけにはいきません。なので昔から使われていて効果の大きいファイルを1つにまとめる方法を実装してみます。この方法は消費メモリが増える欠点があるので、データ圧縮した状態(Zipファイル)でメモリに保存しておき、使用時に展開することでメモリ消費を抑えます。展開にはそれなりの時間がかかるのでWebWorkerで並列処理し、ゲームループなどのアニメーション処理に影響が出ないようにします。展開処理は WebWorker から呼ばれるRust(wasm)の関数で行います。
ファイル読み込み時間の測定
画像ファイル(200kb)5枚を読み込む時間を測定してみました。サーバーや回線状況に大きく左右されるため誤差が大きく、正確な測定はできませんでした。1ファイルずつ読み込んだ時の時間を基準(100%)にしています。
1ファイルずつ順番に読み込み 100% (656ms)
同時要求を1つに制限し、サーバーに負荷をかけない方法での読み込みです。1番時間の掛かる方法。
5ファイル同時要求 20%~50%
サーバーに最も負荷をかける方法です。最速ですが回線状況などに大きく影響されます。
非圧縮Zipファイル 30%~40%
圧縮なし、展開処理が不要な方法です。ネットワーク通信はgzipなどで圧縮してから行うため、通信時間は圧縮した場合とほぼ同じになります。ファイルが1つなので回線状況の影響小、メモリ消費大。
圧縮Zipファイル 30%~40%
無圧縮に比べて遅くなると思ったのですが、展開時間より回線状況の影響が大きく測定結果に現れませんでした。この方法を採用して問題ないようです。
WebWokerからWebassembly(Rust)呼び出し
WebWorkerは使える機能が限定(DOM操作不可など)されていますが、Webassemblyは問題なく使えるようです。Workerを使わない場合と同じ方法で利用できます。
TypeScriptのpostMessage関数の引数エラー
DOMのpostMessageと認識されてコンパイルエラー、関数の再定義で回避。
declare function postMessage(message: any, trans? : any) : void;
エラー処理
エラー処理をしないとJavaScriptのシンタックスエラーがあっても無言のままです。Worker作成側がエラーメッセージを受け取り、エラー処理(console出力など)をします。Promiseで発生したエラーではエラーメッセージは発行されないので、別途エラー処理が必要になります。
let worker = new Worker("srwfs_worker.js");
worker.addEventListener('error', onError, false);
ダウンロード
Zipファイルで圧縮された 画像ファイル5枚をWebWorkerの並列処理で展開してから表示するプログラムです。圧縮方式はDeflateのみ対応。時間測定のためHTTPキャッシュ無効。
fetchを繰り返し実行する際の注意点
時間測定とメモリリーク検出のために、短い間隔(1秒毎)でfetchを連続実行していると、PCの動作が重くなるという現象が発生しました。タスクマネージャーで調べてみるとブラウザ(FireFox)の使用メモリが2GB近くまで増えたことによるメモリ不足が原因でした。メモリリークを疑ったのですがGC強制実行で使用メモリ減少、ブラウザのメモリデバッグでもリークは検出されませんでした。ネットで調べてみたのですが、同様の現象が発生したと質問があるのですが、解決策の回答はありません。
色々な方法を試しながら調べてみると、推測ですが原因らしきものがわかってきました。 一番の対策は、連続して短い間隔で呼ばないです。数秒やそれ以下のfetchの長時間連続実行はサーバー攻撃といっていいと思います(ローカルWebサーバーでのテストで気付いてよかった)。
fetch(やXHR)で使用したメモリは、通常のGCでは回収されず、メモリの使用限界を超えたときや一定時間操作がないときに実行されるGCでしか回収されないようです。短時間で繰り返し実行することでGCを実行する機会が少なくなり限界までメモリを確保、限界越えのGCでやっとメモリ回収。このためブラウザの使用メモリが1GB~2GBの間を行ったり来たりしていたようです。
詳しく調べていませんが、Chormeでも似たような使用メモリの増加現象が発生するようです。Edgeでは発生しません。
ブラウザの設定で操作なしの判定時間やメモリ限界値を変更できますが、やはり短い間隔で呼ばないことが最善の解決策だと思います。ゲーム制作で問題になりそうなのは、ローディングが発生する建物への出入りを繰り返すなどプレイヤーの操作によって読み込みが連続発生するような状況です。なにかしらの対策が必要になりそうです。ブラウザゲームで オープンワールド系のゲームとかできたら面白そうだけど 、最初に使用するデータをすべて読み込む方式が無難かも。