Quantcast
Channel: CSSタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8933

要素数10万のHTMLページを使ってCSSセレクターの照合にかかる時間を測定した

$
0
0
querySelectorAll()を使ってCSSセレクターのパフォーマンスを測定した結果を紹介した記事があったので、自分でもやってみました。 実行環境 Chrome 96 macOS Big Sur 11.6.2 iMac 2017 / CPU 3.5GHz quad-core Intel Core i5 / メモリ32GB 参考にしたのはこの記事とコードです。 [資料1] Optimizing CSS: ID Selectors and Other Myths [資料2] CodePen: CSS selector performance CSSセレクターのパフォーマンスの観点で私が以前から気になっていたのが、たとえば以下のようなマークアップでa要素にスタイルをあてる場合です。 html <ul> <li>The quick <a href="#">brown fox</a> jumps over the lazy dog</li> </ul> 要素の親子関係を使って「このa要素」を表すCSSセレクターを書く場合、ul, li, a要素にclassを割り当ててあるか・いないかによって選択肢がいくつもあります。たとえばul要素だけclassを割り当てて、.list li aや.list aと書くなど。一方、ブラウザがセレクターを解釈する処理は右から左へ行われるらしいので、このようなセレクターの形式は要素の照合に時間がかかってパフォーマンスが低下してしまうのでは?と気になったりもします。そこで、セレクターの書き方によって要素の照合にかかる時間がどれだけ増えるのか、要素数10万のHTMLページを作って実際に測って確かめてみることにしました。 結論から言うと、li要素の子のa要素については、10万要素1のHTMLページであってもセレクターの書き方の違いによるパフォーマンスの差は数ミリ秒程度のものでした。 測定方法 テスト用のHTMLページ テスト用のページは以下の構造を持つdiv.boxを10,000個配置したもので、HTML要素数は全体で約100,000個になります。ここで${count}は各div.boxに対して割り当てた連番(1〜10,000)が入ります。 html <div class="box b${count}"> <div class="title"><p>${count}</p></div> <ul class="list"> <li class="item first"><a href="#" class="link">_</a></li> <li class="item active"><a href="#" class="link">_</a></li> <li class="item last"><a href="#" class="link">_</a></li> </ul> </div> 実際のページはこれです。資料2のコードからforkして今回の測定用に改造しました。 Part 1: https://codepen.io/kaz_hashimoto/pen/rNGGKJj Part 2: https://codepen.io/kaz_hashimoto/pen/rNGYrvJ 画面のMeasureボタンをクリックすると測定を開始し、DevToolsのコンソール画面に結果を表形式で出力します。測定は、querySelectorAll()の呼び出し前後のタイムスタンプの差の平均値(単位ミリ秒)です。試行回数はPart 1が20回、Part 2が30回です。 javascript function test(selector) { const t0 = performance.now(); document.querySelectorAll(selector); const t1 = performance.now(); const msec = t1 - t0; return msec; } テスト項目と測定結果 今回テストした項目と測定結果の要約は下表のとおりです。「li要素の子のa要素」に対するセレクター(項目No.1)に加えて、パフォーマンスの観点から気になっていた項目(No.2〜8)も追加しました。 テストページPart 1 # 概要 結果 1 ul>li>a要素を選択する 3.1〜5.1ms 2 擬似クラスを付けてul>li>a要素を選択する 4.4〜4.8ms 3 兄弟結合子を使ってdivを選択する 2.5〜2.6msただし、特定のパターンで596ms テストページPart 2 # 概要 結果 4 多数のclassを持つ要素に対してclass名で選択する 5.6〜6.8ms 5 長いclass名を持つ要素に対してclass名で選択する 3.1〜4.6ms 6 孤立したclass名をセレクターの前に付けて選択する 1.6〜4ms 7 孤立したclass名をセレクターの前に付けて選択する(#6 + #4) 6.6〜9.4ms 8 孤立したclass名をセレクターの前に付けて選択する(#6 + #5) 3.7〜6.2ms ※「孤立したclass名」とはHTMLページのどの要素からも参照されないclass名のことです。 結果の詳細 Test#1: ul>li>a要素を選択する セレクターの組み合わせパターン Test#1で対象とする「li要素の子のa要素」を表すセレクターのパターンについては、以下の5つの項目の組み合わせで構成されるセレクターとしました。他にもIDセレクターや全称セレクターが含まれるケースも考えられますが、キリがないので除外しました。 要素を直接指定: ul, li, a class名を指定: .list, .item, .link 要素とclass名を指定: ul.list, li.item, a.link 子孫結合子(スペース)を指定: ul li a, .item aなど 子結合子「>」を指定: ul > li > a, .item > aなど querySelector()の戻り値がundefinedになるものを除外すると、有効なセレクターは全部で138個になりました(組み合わせを生成するソースコードが正しければ…)。 測定結果 138個のセレクターについての測定結果のconsole出力を下図に示します。行の順序はtime値(ミリ秒)の昇順です。specificityはセレクターの詳細度です。 まずは速い方から。 速いセレクター上位20 a要素にclassを付けない場合は、祖先のul要素とのペアよりも親要素liと組み合わせたパターンにする方がパフォーマンスが若干良いようです。 遅いセレクター下位20 パターンが複雑になり詳細度の下2桁が大きいセレクターでは、プラス1〜2ms処理時間が増えてきました。ul.listのように「要素名.class名」と言う形式は詳細度が増えて扱いづらいだけでなく、パフォーマンス的にも不利なのがよくわかりました。 Test#2: 擬似クラスを付けてul>li>a要素を選択する 次に、li要素の兄弟要素のグループ内での位置を指定してa要素を選択するケースです。liに擬似クラス:nth-child(), :first-child, および:last-childのいずれかを付けた場合と、位置を表すclass名で代用した場合とを比較しました。 html <ul class="list"> <li class="item first"><a href="#" class="link"></a></li> <li class="item active"><a href="#" class="link"></a></li> <li class="item last"><a href="#" class="link"></a></li> </ul> class名で代用したセレクターと比較して、擬似クラスを用いたセレクターは相対的にやや速度が落ちるようです。 Test#3: 兄弟結合子を使ってdivを選択する 次は、10,000個のdiv.boxに対して、隣接兄弟結合子(+)および一般兄弟結合子(~)を使ったセレクターで要素を選択した場合です。div.box要素には連番でそれぞれ一意のclass名b1, b2, ...を振ってあります(下図)。 先行要素に".b5000"を持つ後続要素".box"を選択するセレクター".b5000 ~ .box"だけが非常に遅く、約596msもかかっています。 Test#4: 多数のclassを持つ要素に対してclass名で選択する 次は、各要素にclassを30個付けた状態でそのうちの1つのclassをセレクターに指定した場合です。 Before: 30個のclassを付ける前の状態でテスト まずは比較のため、classが1〜2個しか付いていない初期状態で実行した時の結果です。 After: classを30個付けた状態でテスト こちらは要素にclassを30個付けて、セレクターには15番目のclass名を指定して要素を選択した時の結果です。 要素のみのシンプルなセレクター(div, ul, li, a)はBefore/After共に2ms程度で増加も0.7ms以下なのに対して、class名を指定した方は3〜4ms遅くなっています。 Test#5: 長いclass名を持つ要素に対してclass名で選択する 次は、各要素に30文字程度の長いclass名を付けてそのclass名をセレクターに指定した場合です。 初期状態(Test#4 Beforeの表)よりはスピードが落ちますが、classが30個のケース(Test#4)ほどには低下しません。 Test#6: 孤立したclass名をセレクターの前に付けて選択する Test#6〜#8は、どのHTML要素からも参照されていないclass名".notdef"をセレクターの前に付けた場合です。以下のような事例を想定しました。 「処理の状態」や「機能の有無」を表す文字列をJavaScriptを使ってbody要素のclassに追加しているページ CSSファイルを流用したため、そのHTMLページで使われていないCSSルールが大量にある。 まずはシンプルなセレクターに".notdef"を付けた場合。 Test#4 Beforeの表と比べて、セレクターのスピードに目立った低下はありません。 Test#7: 孤立したclass名をセレクターの前に付けて選択する(#6 + #4) 次に、要素が多数のclassを持っているページで(Test#4)、".notdef"をセレクターの前に付けた場合です。 シンプルなセレクターも同様にTest#6の結果と比べて4〜5ms遅くなっています。 Test#8: 孤立したclass名をセレクターの前に付けて選択する(#6 + #5) 最後は、要素が長いclass名を持っているページで(Test#5) 、".notdef"をセレクターの前に付けた場合です。 シンプルなセレクターも同様にTest#6の結果と比べて1ms程度遅くなっています。 JSライブラリ 測定結果に出力されるセレクターの詳細度については、以下のライブラリを使用しました。 Specificity Calculator 0.4.1 GitHub: https://github.com/keeganstreet/specificity CDN: https://unpkg.com/specificity@0.4.1/dist/specificity.js ちなみに某ニュースサイトのページでbody要素の下のノード数をquerySelectorAll()でカウントすると3000〜3500個くらいです。 ↩

Viewing all articles
Browse latest Browse all 8933

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>