Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
レンダリングパフォーマンス
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Benoît Quenaudon
September 19, 2016
Programming
120
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
レンダリングパフォーマンス
Benoît Quenaudon
September 19, 2016
More Decks by Benoît Quenaudon
See All by Benoît Quenaudon
36・15 Cash App
oldergod
1
56
Instant Apps Eulogy
oldergod
1
53
36・15 Cash App
oldergod
1
270
Heuristics in Everyday Life
oldergod
1
59
Sweet Architecture
oldergod
1
490
Architecture at Scale (droidconNYC 2022)
oldergod
2
720
Managing gRPC with Wire
oldergod
2
660
Wire & Proto3
oldergod
0
140
Effective Reactive Architecture
oldergod
2
260
Other Decks in Programming
See All in Programming
<title><a id="</title>君はこのHTMLをパースできるか"></a></title> #雑LT_study
pizzacat83
0
150
PostgreSQL 18で考えるUUID主キー
kazuhiro1982
0
480
freee が目指す データ マネジメント戦略 AI-Ready 時代を支える 攻めのガバナンスとは
freee
PRO
0
430
php-fpmのプロセスが枯渇した日-調査・対処・そして本当にやるべきだったこと-
shibuchaaaan
0
320
Detecting Compromised CI with eBPF and Cilium Tetragon
lizrice
0
210
人間の目はかわらない、だからJPEGは30年もつ
yuzneri
12
19k
Built Our Own Background Agent at LayerX
layerx
PRO
10
5.8k
ここ半年くらいでAIに作らせたR用ツール
eitsupi
0
390
改善しないと、タスクが回らない。 “てんこ盛りポジション” を引き継いだ情シスの、入社3ヶ月の業務改善録
krm963
0
280
ソフトウェア設計に溶けるインフラ ― AWS CDK のインフラ認識論
konokenj
3
810
テーブルをDELETEした
yuzneri
0
150
Go言語とトイモデルで学ぶTransformerの気持ち / fukuokago23-transformer
monochromegane
0
190
Featured
See All Featured
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
3
450
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
470
How GitHub (no longer) Works
holman
316
150k
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
YesSQL, Process and Tooling at Scale
rocio
174
15k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3k
Being A Developer After 40
akosma
91
590k
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.5k
Git: the NoSQL Database
bkeepers
PRO
432
67k
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.9k
Producing Creativity
orderedlist
PRO
348
40k
Transcript
Rendering Performance #perfmatters ケノドン ブノア @oldergod
None
60fps 閾値 • 主のデバイスは1秒に画面を60回リフレッシュしている ◦ 1 frame = 16.6ms(1000ms ÷
60) • アニメーション中にフレームを作る処理が16msを超えたらフ レームレートが低下し、画面はガタツキが発生する ⇒ ジャンク(Jank)と呼ぶ ◦ 多くなるとUXにマイナスな影響がでる
Layer って何?
• Javascript:視覚的に変化を起こす処理(javascriptに限らず) • Style:CSSルールがどの要素に一致するかを、各要素の最終的なスタイ ルを算出する • Layout(reflow):Styleによって各要素は、どのくらいのスペースが必要 か、画面内の位置、などを計算し始める • Paint:レイヤーのピクセルを書き込む処理
• Composite:全レイヤーを組み合わせ、正しくレンダリングする Pixel Pipeline
JS > Style > Layout > Paint > Composite •
パフォーマンス的に何ができるか? • どこでネックができたり、どう解決するか? • そもそもすべてのジョブを通す必要があるか? • JS / Style / Composite:必須 ⇒ Layout、Paintについては? ◦ 利用するスタイルによってスキップできる?
JS > Style > Layout > Paint > Composite 「レイアウト」プロパティの変更
⇒ • 幅、高さ、左または上の位置などに従って、ブラウザは他のす べての要素との影響を確認する必要がある • 影響する領域は再ペイントする必要がある
JS > Style > Paint > Composite 「ペイントのみ」プロパティの変更 ⇒ •
背景画像、テキストの色、影などの変更 • ページのレイアウトに影響を与えないものはスキップ • ペイントは行われる
JS > Style > Composite 「コンポジットのみ」プロパティの変更 ⇒ • ブラウザは合成を行うためにジャンプしる 任意の
CSS プロパティを変更すると上記の 3 つのバージョン のどれが実行されるかが知りたい方 ⇒ https://csstriggers.com/
Pixel Pipelineのベストを尽くす 各タスクに対して何ができるかを 細かくみてみよう
JavaScript
Animation with JavaScript • アニメーションに setTimeout、setInterval が使われる事はある があかん:いつ実行されるかわからない
Animation with JavaScript • 60fps よりはやい setInterval は無駄 • 60fps
より遅い setInterval はガタツキ • setInterval(16.6) はブラウザに同期しないとガタツキ • ヨーロッパは50Hzのデバイスがまだ多い ⇒ ブラウザのペースに合わせた requestAnimationFrame を使お う。
Window.requestAnimationFrame() function step() { // アニメーション処理, e.g. translateX += n;
等 // 更に次のフレームにも実行する処理を登録 requestAnimationFrame(step); } // 次のフレームの頭に実行しておきたい処理を登録 requestAnimationFrame(step);
requestAnimationFrame() サポート • IE10 ≧ OK • 他のブラウザは前からOK • Polyfillも書ける
⇒ requestAnimationFrame を使おう
Main Thread(又は UI Thread) • Main Thread:スタイル計算、レイアウト、ペイントと一緒にJSが 実行される • そのすべてが16ms以内に終わらない
⇒ ジャンク • JavaScript の実行中は他のタスク(UI系も含め)がブロックされ る
レンダリングとJavaScriptの関係 - 実験 http://oldergod.github.io/ui-rendering-opt/demos/main-thread/index.html
Main Thread(又は UI Thread) ⇒ DOMアクセスが必要のない処理は Web Worker に移動 ◦
ロード処理、検索、ソートとかに使える!
Web Worker ➕ 別スレッドで動く ➕ レンダリングに影響がでない ➕ XMLHttpRequest可能 ➖ DOMアクセス不可能
どうしてもメインスレッドで実行したい場合はバッチ的に小さいタス クを requestAnimationFrame で実行するのがおすすめ
Web Worker デモ http://oldergod.github.io/ui-rendering-opt/demos/web-workers/index.html
// worker の登録 const worker = new Worker('scripts/worker.js'); // manipulateImage
にて worker にメッセージを送信 worker.postMessage({ type, imageData }); // メッセージの受信時 worker.onmessage = function(message) { const imageData = message.data; toggleButtonsAbledness(); ctx.putImageData(imageData, 0, 0); }; // worker の終了 worker.terminate(); Web Worker デモ function onmessage(data); function postMessage(data); Web Worker Thread
// worker の登録 const worker = new Worker('scripts/worker.js'); // manipulateImage
にて worker にメッセージを送信 worker.postMessage({ type, imageData }); // メッセージの受信時 worker.onmessage = function(message) { const imageData = message.data; toggleButtonsAbledness(); ctx.putImageData(imageData, 0, 0); }; // worker の終了 worker.terminate(); Web Worker デモ function onmessage(data); function postMessage(data); Web Worker Thread
// worker の登録 const worker = new Worker('scripts/worker.js'); // manipulateImage
にて worker にメッセージを送信 worker.postMessage({ type, imageData }); // メッセージの受信時 worker.onmessage = function(message) { const imageData = message.data; toggleButtonsAbledness(); ctx.putImageData(imageData, 0, 0); }; // worker の終了 worker.terminate(); Web Worker デモ function onmessage(data); function postMessage(data); Web Worker Thread
// worker の登録 const worker = new Worker('scripts/worker.js'); // manipulateImage
にて worker にメッセージを送信 worker.postMessage({ type, imageData }); // メッセージの受信時 worker.onmessage = function(message) { const imageData = message.data; toggleButtonsAbledness(); ctx.putImageData(imageData, 0, 0); }; // worker の終了 worker.terminate(); Web Worker デモ function onmessage(data); function postMessage(data);
// worker の登録 const worker = new Worker('scripts/worker.js'); // manipulateImage
にて worker にメッセージを送信 worker.postMessage({ type, imageData }); // メッセージの受信時 worker.onmessage = function(message) { const imageData = message.data; toggleButtonsAbledness(); ctx.putImageData(imageData, 0, 0); }; // worker の終了 worker.terminate(); Web Worker デモ function onmessage(data); function postMessage(data); Web Worker Thread
Web Worker サポート • IE10 ≧ OK • 他のブラウザは前からOK
Layout
複雑なLayout、Layout Thrashing • Layoutのスコープは基本、documentのすべて※ ◦ 一つだけ弄れば、全部が再計算される ◦ アイテムが多ければ多いほどコストが高まる ⇒ 可能な限りレイアウトを回避
◦ Layoutを実行しないスタイルプロパティを利用する ◦ DevToolsで調査して最善を尽くす
Layout Thrashing デモ Layout Thrashing:強制的な同期レイアウトを数多く実行する事 http://oldergod.github.io/ui-rendering-opt/demos/layout-thrashing/index.html
強制的な同期レイアウトの原因 while (i--) { paragraphs[i].style.width = greenBar.offsetWidth + 'px'; }
強制的な同期レイアウトの原因 paragraphs[i + 1].style.width = greenBar.offsetWidth + 'px'; paragraphs[i].style.width =
greenBar.offsetWidth + 'px';
強制的な同期レイアウトの原因 offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i + 1].style.width =
offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
強制的な同期レイアウトの原因 offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i + 1].style.width =
offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
強制的な同期レイアウトの原因 // 最後に計算された layout 情報 offsetWidth = greenBar.offsetWidth + 'px';
paragraphs[i + 1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
強制的な同期レイアウトの原因 // 最後に計算された layout 情報 offsetWidth = greenBar.offsetWidth + 'px';
paragraphs[i + 1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
強制的な同期レイアウトの原因 // 最後に計算された layout 情報 offsetWidth = greenBar.offsetWidth + 'px';
// style を変更 ⇒ layout が無効化 paragraphs[i + 1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
強制的な同期レイアウトの原因 // 最後に計算された layout 情報 offsetWidth = greenBar.offsetWidth + 'px';
// style を変更 ⇒ layout が無効化 paragraphs[i + 1].style.width = offsetWidth; offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
強制的な同期レイアウトの原因 // 最後に計算された layout 情報 offsetWidth = greenBar.offsetWidth + 'px';
// style を変更 ⇒ layout が無効化 paragraphs[i + 1].style.width = offsetWidth; // layout が無効なため、再計算 offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
強制的な同期レイアウトの原因 // 最後に計算された layout 情報 offsetWidth = greenBar.offsetWidth + 'px';
// style を変更 ⇒ layout が無効化 paragraphs[i + 1].style.width = offsetWidth; // layout が無効なため、再計算 offsetWidth = greenBar.offsetWidth + 'px'; paragraphs[i].style.width = offsetWidth;
強制的な同期レイアウトの原因 // 最後に計算された layout 情報 offsetWidth = greenBar.offsetWidth + 'px';
// style を変更 ⇒ layout が無効化 paragraphs[i + 1].style.width = offsetWidth; // layout が無効なため、再計算 offsetWidth = greenBar.offsetWidth + 'px'; // style を変更 ⇒ layout が更に無効化、etc. paragraphs[i].style.width = offsetWidth;
強制的な同期レイアウトの原因 // 最後に計算された layout 情報 offsetWidth = greenBar.offsetWidth + 'px';
// style を変更 ⇒ layout が無効化! paragraphs[i + 1].style.width = offsetWidth; // layout が無効なため、再計算 offsetWidth = greenBar.offsetWidth + 'px'; // style を変更 ⇒ layout が無効化! paragraphs[i].style.width = offsetWidth;
強制的な同期レイアウトの原因 while (i--) { paragraphs[i].style.width = greenBar.offsetWidth + 'px'; }
let offsetWidth = greenBar.offsetWidth + 'px'; while (i--) { paragraphs[i].style.width = offsetWidth; }
強制的な同期レイアウトの原因 • Layout系の情報は最後のフレーム時のデータ • Style を更新すると、Layoutの情報が無効となり、Layout再計算 が必要となる ⇒ Style、Layoutの取得を一回のみ、最初にしておく事 ⇒
コードを書く時にPixel Pipelineの順序を考慮しよう
Paint
ペイントをシンプルにする • ペイントはパイプライン内のすべてのタスクのうちで最も長く実 行される
• ブラウザは複数のレイヤー、必要な場合はコンポジ層だけに対 してペイントを行うことができる ⇒ 再ペイントされるもの、移動するものを、他のコンポネントに影 響を与えないで処理できる! ペイントの対象を絞る レイヤーはどう生成できる?
Composite
レイヤーの作る方法 1. 最良: will-change: transform; 2. ハック:transform: translateZ(0); ◦ will-changeをサポートしていないブラウザに利用
コンポジットのみプロパティを使用 ⚠ 条件:プロパティを変更するに要素が自身コンポジタのレイ ヤーでなければならない! ⇒ その要素をプロモートし、レイヤーを作る
コンポジットのみプロパティを使用 • Position transform: translate(xpx, ypx); • Scale transform: scale(n);
• Rotation transform: rotate(ndeg), • Skew transform: skew(X|Y)(ndeg), • Matrix transform: matrix(3d)(...), • Opacity opacity: 0...1;
レイヤーの作る方法 デモ http://oldergod.github.io/ui-rendering-opt/demos/layout-management/index.html
will-change: transform; か transform: translateZ(0); を使う事で要素 がプロモートされる * { will-change:
transform; transform: translateZ(0); } コンポネントのプロモート // 管理とメモリコストが // 余計に発生するからNG
will-change サポート • IE、Edge:アウト • 他のブラウザは超最近でOK
Rendering Performance まとめ:上 • 改修する前に必ず測る事 • 問題ないものを直すのが無駄 ⇒ 測ってから改修するのが第一
Rendering Performance まとめ:下 • Less work • Scheduled work •
Isolated work • Smooze work ⇒ Happy User
Fin #perfmatters 格好良いエンジニア募集中! http://goo.gl/9GK409
リファレンス • グーグルによるレンダリングパフォーマンス ◦ https://developers.google.com/web/fundamentals/performance/rendering/?hl=ja • 無料ブラウザレンダリング改善コース ◦ https://www.udacity.com/course/browser-rendering-optimization--ud860 •
デモのレポジトリ: ◦ https://github.com/oldergod/ui-rendering-opt • CSSプロパティ詳細 ◦ https://csstriggers.com/ • 効率良いCSS ◦ https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Writing_efficient_CSS • BEM方針 ◦ https://en.bem.info • アニメーションFLIP方法 ◦ https://github.com/googlechrome/flipjs