HTML5のパフォーマンスを最大化する方法

HTML5 は新興分野としてますます人気が高まっています。しかし、モバイルデバイスのハードウェア性能がPCに比べて低い状況では、性能に対する要求がより重要になります。HTML5の最適化前と最適化後のパフォーマンスには大きな違いがあります。パフォーマンスの最適化方法を知っている人はほとんどいません。パフォーマンスを向上させるために。この記事では、例として LayaAir エンジンを使用し、コード例を通じてエンジンを使用して HTML5 のパフォーマンスを最適化する方法を詳しく説明します。

トピックには以下が含まれます:

  • コード実行の基本原則

  • ベンチマーク

  • メモリの最適化

  • グラフィックレンダリングのパフォーマンス

  • CPU使用率を削減する

  • その他の最適化戦略

セクション 1: コード実行の基本原則

LayaAir エンジンは、AS3、TypeScript、JavaScript の 3 つの言語での開発をサポートしていますが、どの開発言語を使用しても、最終的には JavaScript コードが実行されます。表示されるすべての画像はエンジンによって描画され、更新頻度は開発者が指定した FPS によって決まります。たとえば、指定されたフレーム周波数が 60FPS の場合、実行時の各フレームの実行時間は 60 分の 1 秒です。フレームレートが高いほど視覚的に滑らかになり、60 フレームがフルフレームになります。

実際の実行環境はブラウザ内なので、JavaScript インタプリタの効率にも性能が左右されますが、低性能のインタプリタでは指定された FPS フレームレートに達しない可能性があるため、この部分は開発者が決めることはできません。私たちが行っているのは、ローエンド デバイスまたは低パフォーマンスのブラウザーでの FPS フレーム レートを高めるために可能な限り最適化することです。

LayaAir エンジンはすべてのフレームを再描画します。パフォーマンスを最適化するときは、各フレームでロジック コードを実行することによる CPU 消費量に注意することに加えて、各フレームで呼び出される描画命令の数とテクスチャ送信の数にも注意する必要があります。 GPUに。

セクション 2: ベンチマーク

LayaAir エンジンの組み込みパフォーマンス統計ツールをベンチマーク テストに使用して、現在のパフォーマンスをリアルタイムで検出できます。開発者は、laya.utils.Stat クラスを使用して、Stat.show() を通じて統計パネルを表示できます。具体的なコードは次の例に示すとおりです。

Stat.show(0,0); //AS3 パネル呼び出しの書き込みメソッド Laya.Stat.show(0,0); //TS および JS のパネル呼び出し書き込みメソッド

キャンバス レンダリングの統計:

画像の説明

WebGL レンダリングの統計:

画像の説明

統計パラメータの意味:

FPS:

1 秒あたりにレンダリングされるフレームの数 (数値が大きいほど優れています)。
Canvas を使用してレンダリングする場合、説明フィールドには FPS (Canvas) が表示され、WebGL を使用してレンダリングする場合、説明フィールドには FPS (WebGL) が表示されます。

スプライト:

レンダー ノードの数 (数値が小さいほど優れています)。
スプライトはすべてのレンダリング ノード (コンテナを含む) をカウントします。この数のサイズは、エンジン ノードのトラバーサル数、データ構成、およびレンダリングに影響します。

ドローコール:

DrawCall は、キャンバスと WebGL レンダリングで異なる意味を表します (少ないほど良い)。
Canvas では、画像、テキスト、ベクター グラフィックスなど、フレームごとの描画数が示されます。100 未満に制限するようにしてください。
WebGL はレンダリングの送信バッチを表します。データを準備して毎回レンダリングのために GPU に通知するプロセスは DrawCall と呼ばれます。DrawCall ごとに GPU にレンダリングを通知することに加えて、マテリアルやシェーダーの切り替えに時間がかかります。非常に時間がかかります消費動作。DrawCall の数はパフォーマンスを決定する重要な指標であるため、100 未満に制限するようにしてください。

キャンバス:

3 つの値 - フレームごとに再描画されるキャンバスの数 / キャッシュ タイプ「通常」のキャンバスの数 / キャッシュ タイプ「ビットマップ」のキャンバスの数 CurMem: WebGL レンダリングのみ、メモリとビデオ メモリの使用量を示します
。 (多いほど低いほど良い)
シェーダー: WebGL レンダリングのみ。フレームごとのシェーダー送信の数を示します。

Canvas モードでも WebGL モードでも、DrawCall、Sprite、Canvas の 3 つのパラメータに注目し、それに応じて最適化する必要があります。(「グラフィックレンダリングパフォーマンス」を参照)

セクション 3: メモリの最適化

オブジェクトプール

オブジェクト プーリングには、オブジェクトの継続的な再利用が含まれます。アプリケーションの初期化中に一定数のオブジェクトが作成され、プールに保存されます。オブジェクトの作業が終了したら、そのオブジェクトをプールに戻し、新しいオブジェクトが必要になったときに取得できるようにします。
オブジェクトのインスタンス化にはコストがかかるため、オブジェクト プールを使用してオブジェクトを再利用すると、オブジェクトをインスタンス化する必要性が減ります。また、ガベージ コレクターが実行される可能性が減り、プログラムの速度が向上します。

次のコードは、の使用法を示しています。

Laya.utils.Pool:

ar SPRITE_SIGN = 'spriteSign';var sprites = [];functionInitialize(){ for (var i = 0; i < 1000; i++) { var sp = Pool.getItemByClass(SPRITE_SIGN, Sprite) sprites.push 
	( 
		sp); 
		Laya.stage.addChild(sp); 
	初期
();

初期化時にサイズ 1000 のオブジェクト プールを作成します。

次のコードは、マウスをクリックすると表示リスト内のすべての表示オブジェクトを削除し、後で他のタスクで再利用します。

Laya.stage.on("クリック", this, function(){ var sp; for(var i = 0, len = sprites.length; i < len; i++) 
	{ 
		sp = sprites.pop(); 
		Pool.recover (SPRITE_SIGN, sp); 
		Laya.stage.removeChild(sp); 
	} 
});

Pool.recover を呼び出した後、指定されたオブジェクトがプールにリサイクルされます。

使用Handler.create

開発プロセス中、ハンドラーは非同期コールバックを完了するためによく使用されます。Handler.create は組み込みのオブジェクト プール管理を使用するため、Handler オブジェクトを使用するときにコールバック ハンドラーを作成するには Handler.create を使用する必要があります。次のコードは、Handler.create を使用して、ロードされたコールバック ハンドラーを作成します。

Laya.loader.load(urls, Handler.create(this, onAssetLoaded));

上記のコードでは、ハンドラーはコールバックの実行後にオブジェクト プールによって復元されます。この時点で、次のコードで何が起こるかを考えてみましょう。

Laya.loader.load(urls, Handler.create(this, onAssetLoaded), Handler.create(this, onLoading));

上記のコードでは、進行状況イベントは、Handler.create によって返されるハンドラーを使用して処理されます。このときのコールバックは 1 回実行された後にオブジェクト プールによってリサイクルされるため、progress イベントは 1 回だけトリガーされます。このとき、once という名前の 4 つのパラメーターを false に設定する必要があります。

Laya.loader.load(urls, Handler.create(this, onAssetLoaded), Handler.create(this, onLoading, null, false));

空きメモリ

JavaScript ランタイムはガベージ コレクターを開始できません。オブジェクトを確実にリサイクルできるようにするには、オブジェクトへの参照をすべて削除します。Sprite によって提供される destroy は、内部参照を null に設定するのに役立ちます。

たとえば、次のコードは、オブジェクトがガベージ コレクションできることを保証します。

var sp = 新しいスプライト(); 
sp.destroy();

オブジェクトが null に設定されても、メモリからすぐには削除されません。ガベージ コレクターは、システムがメモリが十分に少ないと判断した場合にのみ実行されます。(オブジェクトの削除ではなく) メモリの割り当てにより、ガベージ コレクションがトリガーされます。

ガベージ コレクションは、ガベージ コレクション中に大量の CPU を消費し、パフォーマンスに影響を与える可能性があります。オブジェクトを再利用することで、ガベージ コレクションの使用を制限するようにしてください。また、ガベージ コレクターがオブジェクトを検索する時間を短縮できるように、可能な限り参照を null に設定します。場合によっては (たとえば、2 つのオブジェクトが相互に参照している場合など)、両方の参照を同時に null に設定することは不可能であり、ガベージ コレクターは到達不能なオブジェクトをスキャンしてクリアするため、参照カウントよりも多くのパフォーマンスが消費されます。

リソースのアンロード

ゲームの実行中は常に多くのリソースがロードされます。これらのリソースは、使用後に時間内にアンロードする必要があります。そうしないと、メモリに残ってしまいます。

次の例は、リソースのロード前とロード後のリソースのステータスを比較する方法を示しています。

var 資産 = []; 
資産.push("res/apes/monkey0.png"); 
資産.push("res/apes/monkey1.png"); 
資産.push("res/apes/monkey2.png"); 
資産.push("res/apes/monkey3.png"); 

Laya.loader.load(assets, Handler.create(this, onAssetsLoaded));function onAssetsLoaded(){ for(var i = 0, len =assets.length; i < len; ++i) { varasset =assets 
	[私]; console.log(Laya.loader.getRes(asset)); 
		Laya.loader.clearRes(アセット); console.log(Laya.loader.getRes(asset)); 
	} 
}

フィルターとマスクについて

フィルター効果の使用は最小限に抑えるようにしてください。フィルタ (BlurFilter および GlowFilter) が表示オブジェクトに適用されると、実行時に 2 つのビットマップがメモリ内に作成されます。各ビットマップは表示オブジェクトと同じサイズです。最初のビットマップは表示オブジェクトのラスタライズされたバージョンとして作成され、フィルターが適用された別のビットマップを生成するために使用されます。

画像の説明

フィルタ適用時にメモリ内に 2 つのビットマップが存在する

フィルタまたは表示オブジェクトのプロパティを変更すると、メモリ内の両方のビットマップが更新されて結果のビットマップが作成され、大量のメモリを消費する可能性があります。さらに、このプロセスには CPU の計算が含まれるため、動的に更新されるとパフォーマンスが低下します (「グラフィック レンダリング パフォーマンス – キャッシュAs について」を参照)。

ColorFiter は Canvas レンダリングで各ピクセルを計算する必要がありますが、WebGL での GPU 消費は無視できます。

ベスト プラクティスとして、可能な限り、イメージ オーサリング ツールで作成したビットマップを使用してフィルターをシミュレートします。実行時に動的ビットマップの作成を回避すると、CPU または GPU の負荷を軽減できます。特にフィルターが適用されていて、変更されていない画像。

セクション 4: グラフィックス レンダリング パフォーマンス

スプライトの最適化

1. 不必要なネストのレベルを減らし、スプライトの数を減らしてみます。
2. 表示リストから非表示領域のオブジェクトを削除するか、visible=false を設定してみます。
3. 大量の静的コンテンツまたは頻繁に変更されないコンテンツ (ボタンなど) を含むコンテナーの場合は、コンテナー全体に cacheAs 属性を設定できます。これにより、スプライトの数が大幅に削減され、パフォーマンスが大幅に向上します。動的コンテンツがある場合は、それを静的コンテンツから分離して、静的コンテンツのみがキャッシュされるようにすることをお勧めします。
4. パネルでは、パネル領域外の直接サブオブジェクト(サブオブジェクトのサブオブジェクトは判定不可)は描画されず、パネル領域を越えたサブオブジェクトは消費されません。

DrawCall の最適化

1. キャッシュを設定すると、複雑な静的コンテンツの場合は DrawCall が大幅に削減され、キャッシュを使用することがゲーム最適化の鍵となります。
2. 同じアトラス内のピクチャのレンダリング順序が隣り合うようにしてください。異なるアトラスがクロスレンダリングされると、DrawCall の数が増加します。
3. 同じパネル内のすべてのリソースが 1 つのアトラスを使用するようにしてください。これにより、送信バッチを減らすことができます。

キャンバスの最適化

Canvas を最適化するときは、次の状況では、cacheAs を使用しないように注意する必要があります。

1. オブジェクトが単語や画像など非常に単純であるため、cacheAs=bitmap を設定すると、パフォーマンスが向上しないだけでなく、パフォーマンスの低下も発生します。
2. コンテナ内にアニメーションやカウントダウンなど頻繁に変更されるコンテンツがあり、このコンテナにcacheAs=bitmapを設定するとパフォーマンスが低下します。

Canvas キャッシュが更新されているかどうかは、Canvas 統計の最初の値を確認することで判断できます。

キャッシュとしてについて

cacheAs を設定すると、表示オブジェクトを静的画像としてキャッシュできます。cacheAs を使用すると、サブオブジェクトが変更された場合に自動的に再キャッシュされます。同時に、手動で reCache メソッドを呼び出してキャッシュを更新することもできます。頻繁に変更されない複雑なコンテンツを静的イメージとしてキャッシュすることをお勧めします。これにより、レンダリングのパフォーマンスが大幅に向上します。cacheAs には、「none」、「normal」、「bitmap」の 3 つのオプション値があります。

  1. デフォルトは「none」で、キャッシュは行われません。
    2. 値が「normal」の場合、キャンバスのキャッシュはキャンバスで実行され、コマンドのキャッシュは webgl モードで実行されます。
    3. 値が「ビットマップ」の場合、キャンバス キャッシュはキャンバス モードで使用され、renderTarget キャッシュは WebGL モードで使用されます。ここで、webGL の renderTarget キャッシュ モードには 2048 というサイズ制限があることに注意してください。2048 を超えると、メモリ オーバーヘッドがさらに増加し​​ます。さらに、継続的な再描画のオーバーヘッドは比較的高くなりますが、ドローコールが削減され、レンダリング パフォーマンスが最高になります。WebGL でのコマンド キャッシュ モードは、ノードのトラバーサルとコマンドの構成を減らすだけで、ドローコールは減らしません。また、パフォーマンスは中程度です。

cacheAs を設定した後、staticCache=true を設定してキャッシュの自動更新を防止することもできます。また、手動で reCache メソッドを呼び出してキャッシュを更新することもできます。

cacheAs は主に 2 つの方法でパフォーマンスを向上させます。1 つはノードのトラバーサルと頂点の計算を減らすこと、もう 1 つはdrawCall を減らすことです。CacheAs を有効に活用すると、エンジンのパフォーマンスを最適化するための強力なツールになります。

次の例では、10,000 個のテキストを描画します。

Laya.init(550, 400, Laya.WebGL); 
Laya.Stat.show();var textBox = new Laya.Sprite();var text;for (var i = 0; i < 10000; i++) 
{ 
    text = new Laya.Text(); 
    text.text = (Math.random() * 100).toFixed(0); 
    text.color = "#CCCCCC"; 

    text.x = Math.random() * 550; 
    text.y = Math.random() * 400; 

    textBox.addChild(テキスト); 
Laya.stage.addChild 

(textBox);

以下は筆者のコンピュータでのランタイムのスクリーンショットですが、FPS は 52 程度で安定しています。

以下の例に示すように、テキストが配置されているコンテナーをcacheAsに設定すると、パフォーマンスが大幅に向上し、FPSが60フレームに達します。

// ...他のコードを省略... var textBox = new Laya.Sprite();textBox.cacheAs = "bitmap"; // ...他のコードを省略...

テキストストローク

実行時、ストロークのあるテキストは、ストロークのないテキストよりも描画コマンドを 1 回多く呼び出します。このとき、テキストの CPU 使用率はテキストの数に比例します。したがって、同じニーズを達成するために代替手段を使用するようにしてください。

ほとんど変更のないテキスト コンテンツについては、cacheAs を使用してパフォーマンスの消費を削減できます。「グラフィック レンダリング パフォーマンス - cacheAs について」を参照してください。

内容が頻繁に変更されるが、使用する文字数が少ないテキスト フィールドの場合は、ビットマップ フォントの使用を選択できます。

テキストの書式設定をスキップして直接レンダリングする

ほとんどの場合、大量のテキストは複雑な組版を必要とせず、単にテキスト行を表示するだけです。この需要に応えるために、Text では組版を直接スキップできるchangeText というメソッドが提供されています。

var text = new Text(); 
text.text = "text"; 
Laya.stage.addChild(text);//テキストの内容は後からのみ更新され、changeText を使用するとパフォーマンスが向上します text.changeText("テキストが変更されました。") ;

Text.changeText は、描画命令内のテキストを描画するための最後の命令を直接変更します。以前の描画命令が依然として存在するこの動作により、changeText は次の状況でのみ使用されます。

テキストは常に 1 行です。

テキストのスタイルは常に同じです (色、太さ、斜体、配置など)。

それでも、実際のプログラミングでは、このようなニーズが依然としてよく使用されます。

セクション 5: CPU 使用率を削減する

動的なプロパティの検索を削減する

JavaScript のオブジェクトはすべて動的であり、属性を自由に追加できます。ただし、多数の属性の中から 1 つの属性を検索するには時間がかかる場合があります。特定の属性値を頻繁に使用する必要がある場合は、ローカル変数を使用してそれを保存できます。

関数 foo(){ 
    var prop = target.prop; 
    // prop を使用します process1(prop); 
    プロセス2(プロップ); 
    プロセス3(プロップ); 
}

タイマー

LayaAir には、コード ブロックを実行するための 2 つのタイマー ループが用意されています。

  1. Laya.timer.frameLoop の実行頻度はフレーム周波数に依存し、現在のフレーム レートは Stat.FPS で確認できます。

  2. Laya.timer.loop の実行頻度はパラメータで指定された時間に依存します。

オブジェクトのライフサイクルが終了したら、その内部タイマーを忘れずにクリアしてください。

Laya.timer.frameLoop(1, this, animateFrameRateBased); 
Laya.stage.on("クリック", this, destroy);function destroy() { 
    Laya.timer.clear(this, animateFrameRateBased); 
}

表示されたオブジェクトの境界を取得する方法

相対レイアウトでは、表示されるオブジェクトの境界を正確に取得する必要があることがよくあります。表示されたオブジェクトの境界を取得するにはさまざまな方法があり、その違いを理解しておく必要があります。

1. getBounds/getGraphicBounds を使用します。

var sp = 新しいスプライト(); 
sp.graphics.drawRect(0, 0, 100, 100, "#FF0000");var 境界 = sp.getGraphicBounds(); 
Laya.stage.addChild(sp);

getBounds はほとんどのニーズを満たしますが、境界を計算する必要があるため、頻繁な呼び出しには適していません。

2. コンテナの autoSize を true に設定します。

var sp = 新しいスプライト(); 
sp.autoSize = true; 
sp.graphics.drawRect(0, 0, 100, 100, "#FF0000"); 
Laya.stage.addChild(sp);

上記のコードは、実行時に幅と高さを正しく取得できます。幅と高さが取得され、表示リストの状態が変化すると、autoSize が再計算されます (autoSize は getBoudns を通じて幅と高さを計算します)。したがって、多数のサブオブジェクトを含むコンテナに autoSize を適用することはお勧めできません。サイズが設定されている場合、autoSize は有効になりません。

loadImage を使用した後に幅と高さを取得します。

var sp = 新しいスプライト(); 
sp.loadImage("res/apes/monkey2.png", 0, 0, 0, 0, Handler.create(this, function(){ console.log(sp.width, sp.height); })) 
; 
Laya.stage.addChild(sp);

loadImage は、ロードがトリガーされた後のコールバック関数の後でのみ、幅と高さを正しく取得できます。

3. サイズ設定を直接呼び出します。

Laya.loader.load("res/apes/monkey2.png", Handler.create(this, function(){ var texture = Laya.loader.getRes("res/apes/monkey2.png"); var sp = new Sprite(); 
	sp.graphics.drawTexture(texture, 0, 0); 
	sp.size(texture.width, texture.height); 
	Laya.stage.addChild(sp); 
}));

Graphics.drawTexture を使用しても、コンテナの幅と高さは自動的に設定されませんが、テクスチャの幅と高さを使用してテクスチャをコンテナに割り当てることができます。言うまでもなく、これが最も効率的な方法です。

注: getGraphicsBounds は、ベクター描画の幅と高さを取得するために使用されます。

アクティビティステータスに基づいてフレームレートを変更する

フレーム レートには 3 つのモードがあり、Stage.FRAME_SLOW は FPS を 30 に維持し、Stage.FRAME_FAST は FPS を 60 に維持し、Stage.FRAME_MOUSE は FPS を 30 または 60 フレームに選択的に維持します。

ほとんどの場合、30FPS ですでに人間の視覚の応答を満たせるため、ゲームを 60FPS で実行する必要がない場合もありますが、30FPS ではマウス操作時に画面の一貫性がなくなる可能性があるため、Stage.FRAME_MOUSE が登場しました。

次の例は、ボールがマウスの動きに追従するように、Stage.FRAME_SLOW のフレーム レートでキャンバス上でマウスを移動することを示しています。

Laya.init(Browser.width, Browser.height); 
Stat.show(); 
Laya.stage.frameRate = Stage.FRAME_SLOW;var sp = new Sprite(); 
sp.graphics.drawCircle(0, 0, 20, "#990000"); 
Laya.stage.addChild(sp); 

Laya.stage.on(Event.MOUSE_MOVE, this, function(){ 
	sp.pos(Laya.stage.mouseX, Laya.stage.mouseY); 
});

画像の説明

このときFPSは30と表示されており、マウスを動かすとボールの位置の更新が不安定になっているのが感じられます。Stage.frameRate を Stage.FRAME_MOUSE に設定します。

Laya.stage.frameRate = Stage.FRAME_MOUSE;

画像の説明

この時、マウスを動かした後のFPSは60と表示され、画面の滑らかさが向上します。マウスが 2 秒間動かないと、FPS は 30 フレームに戻ります。

callLater を使用する

callLater は、このフレームのレンダリング前までコード ブロックの実行を遅らせます。現在の操作によってオブジェクトの状態が頻繁に変更される場合は、callLater を使用して計算の繰り返しを減らすことを検討してください。

外観を変更するプロパティを設定すると図が再描画される図について考えてみましょう。

var 回転 = 0、
	スケール = 1、
	位置 = 0;関数 setRotation(value){ this.rotation = 値; 
	アップデート(); 
}関数 setScale(値){ this.scale = 値; 
	アップデート(); 
}関数 setPosition(値){ this.position = 値; 
	アップデート(); 
}関数 update(){ console.log('rotation: ' + this.rotation + '\tscale: ' + this.scale + '\tposition: ' + Position); 
}

ステータスを変更するには、次のコードを呼び出します。

setRotation(90); setScale(2); setPosition(30);

コンソールの出力結果は、

回転:90 目盛:1 位置:0
回転:90 目盛:2 位置:0
回転:90 目盛:2 位置:30

update は 3 回呼び出され、最終結果は正しかったですが、最初の 2 回の呼び出しは不要でした。

3 つの更新を次のように変更してみてください。

Laya.timer.callLater(this, update);

現時点では、update は 1 回だけ呼び出され、これが希望する結果になります。

画像/ギャラリーの読み込み

画像/アトラスの読み込みが完了すると、エンジンは画像リソースの処理を開始します。画像ギャラリーがロードされている場合、各サブ画像が処理されます。多数の画像を一度に処理すると、このプロセスにより長時間の遅延が発生する可能性があります。

ゲームのリソースロードでは、レベルやシーンなどに応じてリソースをロードできます。同時に処理する必要がある画像が少ないほど、そのときのゲームの応答性は高くなります。リソースの使用後、リソースをアンロードしてメモリを解放することもできます。

セクション 6: その他の最適化戦略

1. 使用するパーティクルの数を減らすモバイル プラットフォームのキャンバス モードでは、パーティクルを使用しないようにします。

2. キャンバス モードでは、回転、スケーリング、アルファなどの属性の使用を減らすようにしてください。これらの属性はパフォーマンスを消費します。(WebGL モードで利用可能);

3. タイムループ内でオブジェクトや複雑な計算を作成しないでください。

4. コンテナの autoSize の使用と getBounds() の使用を減らすようにしてください。これらの呼び出しにより、より多くの計算が生成されるためです。

5. try catch によってキャッチされた関数の実行は非常に遅くなるため、try catch はできるだけ使用しないでください。


 

おすすめ

転載: blog.csdn.net/delishcomcn/article/details/132223181