2016年5月24日火曜日

SVGの高速化・生DOMを直接描画するな

戯れ(?)に作ってみた「ラスタ画像SVG化スクリプト:カラー対応版」から判ったこととしては,表示対象のSVGが(比較的)多量の複雑なpath要素から構成されていた場合,その表示方法によってはグラフィック描画パフォーマンスに雲泥の差が発生する,ということ.

※なお, スクリプトによるアニメーションを行う場合はまた違った結果となる.


経緯


mix-blend-modeを使った合成をfilter要素で書き換えるコードを記述していた際、たまたまuse要素を使うロジックにしたところ、出力したSVGの描画速度に驚くほどの差が発生したことに気づく.それまではpath要素の描画が順次行われており,SVGグラフィックの構築過程が観察できていたのが,ほぼ瞬時にスクリーンに表示されるようになった.(※SVGを直接ブラウザで表示した場合)

考察


canvas要素にも言えることだが,スクリーンの逐次書き換えはコストのかかる処理である.HTMLと同じと仮設を立てると,何の対策も施していないSVGではコードがDOMにパースされた後,DOMツリーの内容に従って順次グラフィックが描かれていくはずだ.サイズの大きいHTMLでは大抵の場合,追加すべきグラフィックはスクリーンの範囲外に追いやられてしまうのでさほど影響を及ぼさないが,SVGにおいては常に単一のスクリーンの再描画を行うこととなり,致命的なパフォーマンスの劣化が発生する.

一方defs要素に定義されたグラフィックをuse要素から参照する場合,一旦defs要素内で解析済みの内容をuse要素部に描画するだけであり,その結果スクリーンの書き換え処理の回数を極小化できる.つまり,オフスクリーンレンダリングのような効果が得られる.

検証


複雑なpathから構成される大きなSVGを、(1)「生DOMが直接描画されるように構成」、(2)「defs要素内の図形をuse要素から参照するように構成」の2つで作り、直接ブラウザで表示する.
その際,SVG読み込み開始時とwindowのloadイベント発生時との間の経過時間を計測する.

構造


いずれも同じ構成でおよそ30MBのSVGファイルとした.

(1) 生DOMが直接描画されるように構成

<svg>
<script>
var start = Date.now();
window.onload = function(){console.log(Date.now() - start);}
</script>
<g><path/><path/><path/>・・・</g>
<g><path/><path/><path/>・・・</g>
<g><path/><path/><path/>・・・</g>
</svg>

(2)defs要素内の図形をuse要素から参照するように構成

<svg>
<script>
var start = Date.now();
window.onload = function(){console.log(Date.now() - start);}
</script>
<defs>
<g id="r"><path/><path/><path/>・・・</g>
<g id="g"><path/><path/><path/>・・・</g>
<g id="b"><path/><path/><path/>・・・</g>
</defs>
<use xlink:href="#r"/>
<use xlink:href="#g"/>
<use xlink:href="#b"/> 
</svg>

結果 


いずれも10回表示し、その平均をとった結果.

Firefox
Chrome

(1) (2) (1) (2)

15984 4298 989 948

15985 4250 1018 958

15407 4187 842 966

16471 4162 844 971

16098 4251 939 964

15420 4504 793 962

15879 4386 837 963

16522 4280 1317 953

15801 4385 899 963

15884 4566 1064 967
平均 15945.1 4326.9 954.2 961.5

Firefoxでは実に3倍以上の差が発生した.
Chromeではloadイベント発生時とグラフィック完成時との間に差異があるため,有意な差が無いように見える.むしろ(2)のほうがuse要素の分DOM解析に時間がかかっている.が,目視では明らかに(2)のほうが早い.

結論 

  • SVGを直接ブラウザで表示する(タブ内,iframe内,object内)場合,
    図形を直接描画するよりも一旦defs要素で定義し,その内容をuse要素で複写した方が(初期)描画パフォーマンスが上がる.
    [条件]
    • 比較的多数の図形要素から構成されている.
    • 含まれているpath要素の形状が複雑である.
    ※理屈上、いかなる場合にも適用可能だが、構成が単純な場合その恩恵はごくわずか.
  • なおimage要素やimg要素によるレンダリングではまた違った結果となる気がする. (未検証)
  • なお,アニメーションを行う場合はこの限りではない.あくまで静的で複雑なグラフィックを表示する際に有効.

まとめ


SVGの動作パフォーマンスを改善させるための優先順位
  1. DOMノード数を減らす(ノード数≒描画回数)
  2. 暗黙のレンダリングを回避する(markerの使いすぎ等)
  3. defs-useを使ったオフスクリーンレンダリングを活用する(New!)→但しこれは静的なグラフィックに限る!
  4. DOMの編集は最小手数で.一端ノードツリーをDOMから切り離して編集・再挿入.
    アニメーション処理で直接プロパティを操作するのは厳禁.
    ・JavaScript による SVG への実行時描画を高速化する
    http://devadjust.exblog.jp/22220156/
    での理屈とも合致する.
    ※innerHTMLによる書き換えはやり過ぎると強烈なdomガベージが発生するのであまり良くない.
  5. パスの複雑性を減らす(曲線部を減らす,精度を落とす)
  6. 適切なg要素の階層化
こんなところか

0 件のコメント:

コメントを投稿