2007/09/24

DOM検索効率化をもう少し追ってみる。

やったよ。やった。ようやくキーボードが届いたよ。ここ3ヶ月、私のキーボードはC-xと]}のキーが壊れていたのだけど、ようやく快適に打てるようになったよ。私の状態を見兼ねて買ってくれた学生時代の恩師の稲葉先生どうもありがとうございます。つーか、そのくらい自分で買えばいいのだけど、なんか買う気がなかなか起こらなくて、ダラダラしてました。。。

で、さっそく簡単なプログラムを書きはじめている。えと、実はまだDOM検索周りを調べているのだけど、一つ疑問があがってきた。それは以下の通り。

childNodesでの検索とfirstChildとnextSiblingの検索について。つまり、どちらでも子ノードを取得することができるのだけど、実際どっちを使ったらいいの?

ググってみるとパフォーマンスを見ている人がいたのだが、childNodesよりも、fistChildを取ってnextSiblingで検索する方が速いということらしい。特に、IE6が良くないとのこと。ブラウザの実装によるのね。。。
new Blah().list(); node.childNodes[] performance
うーむ。てっきりnextSiblingって、いかにもリンクリストな感じでその要素の数を線形探索しないといけないから、O(log(n))で、childNodesでindex指定で取得したら、O(1)で速いんだろうなぁ、なんて思っていたのだけど、どうやらそうでないみたい。。。と鵜呑みにするのもどうか、と思うのだが、まぁ、いいや。

ええと、何でこんなことを調べているかと言うと、私はchildNodesからelementNodeだけの配列を取得するようなメソッドが欲しかったので、作ろうと思っていたのだ。そして、その際に、firstChildからnextSiblingでグルングルン回すか、childNodes分こっちもグルングルン回するかどっちにしようかな、と思っていたのだ。そこで自分でベンチマークを取ってみたよ。まぁ、結局、全てを線形探索するのだから、どっちでもいいような気がするけども。。。で、ベンチマークツールはamachang氏のbenchmark.jsを使用した。

ソースはこんな感じ。ここにサンプルも置いておくよ。childNodes && firstChild + nextSibling Benchmark

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  2. <html lang="ja">
  3.     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  4.     </meta><meta http-equiv="Content-Style-Type" content="text/css">
  5.     </meta><meta http-equiv="Content-Script-Type" content="text/javascript">
  6.     <script language="JavaScript" type="text/javascript" src="js/prototype.js"></script>
  7.     <script language="JavaScript" type="text/javascript" src="js/scriptaculous.js"></script>
  8.     <script language="JavaScript" type="text/javascript" src="benchmark.js"></script>
  9.     <script language="JavaScript" type="text/javascript">
  10.       Element.addMethods({
  11.         _childElementsByFirstChildNextSibling: function(element) {
  12.           var children = [];
  13.           var child = element.firstChild;
  14.           while (child) {
  15.             if (child.nodeType == 1) {
  16.               children.push(child);
  17.             }
  18.             var child = child.nextSibling;
  19.           }
  20.           return children;
  21.         },
  22.         _childElementsByChildNodes: function(element) {
  23.           var children = [];
  24.           var nodes = element.childNodes;
  25.           var child = null;
  26.           for (var i = 0, l = nodes.length; i <l; i++) {
  27.             child = nodes.item(i);
  28.             if (child.nodeType == 1) {
  29.               children.push(child);
  30.             }
  31.           }
  32.           return children;
  33.         },
  34.         _childElementByChildNodes: function(element, index) {
  35.           var nodeIndex = 0;
  36.           var nodes = element.childNodes;
  37.           var child = null;
  38.           for (var i = 0, j = 0, l = nodes.length; i <l; i++) {
  39.             child = nodes.item(i);
  40.             if (child.nodeType == 1 && index == j++) {
  41.                return child;
  42.             }
  43.           }
  44.           return null;
  45.         },
  46.         _childElementByFirstChildNextSibling: function(element, index) {
  47.           var nodeIndex = 0;
  48.           var child = element.firstChild;
  49.           while (child) {
  50.             if (child.nodeType == 1 && index == nodeIndex++) {
  51.               return child;
  52.             }
  53.             child = child.nextSibling;
  54.           }
  55.           return null;
  56.         }
  57.       });
  58.     benchmark({
  59.         'initialize': function() {
  60.           for (i = 0; i <5000; i++) {
  61.             $('main').appendChild(Builder.node('div', 'item:' + i));
  62.           }
  63.         },
  64.         'firstChild + nextSibling childElements': function() {
  65.           $A($('main')._childElementsByFirstChildNextSibling()).each(function(e) {
  66.             e.style.margin = '0.5em';
  67.           });
  68.         },
  69.         'childNodes childElements': function() {
  70.           $A($('main')._childElementsByChildNodes()).each(function(e) {
  71.             e.style.margin = '0.5em';
  72.           });
  73.         },
  74.         'firstChild + nextSibling childElement': function() {
  75.            var ce = $('main')._childElementByFirstChildNextSibling(50);
  76.            ce.style.backgroundColor = '#ddd';
  77.         },
  78.         'childNodes childElement': function() {
  79.            var ce = $('main')._childElementByChildNodes(50);
  80.            ce.style.backgroundColor = '#ddd';
  81.         },
  82.         'prototype.js down + next': function() {
  83.           var ce = $($('main').down()).next(49);
  84.            ce.style.backgroundColor = '#ddd';
  85.         }
  86.    });
  87.     </script>
  88.     <title>childNodes && firstChild + nextSibling Benchmark + next</title>
  89.   </script></meta></head>
  90.   <body>
  91.     <h1 style="text-align:center;">childNodes && firstChild + nextSibling Benchmark</h1>
  92.     <div id="main"></div>
  93.   </body>
  94. </html>

firefox2.0.0.7の私の環境での結果は。。。

preparing ...
let's go!
.
*** initialize ***
result : 2763.966739[ms]
.
*** firstChild + nextSibling childElements ***
result : 680.966739[ms]
.
*** childNodes childElements ***
result : 400.966739[ms]
.
*** firstChild + nextSibling childElement ***
result : 48.991075[ms]
.
*** childNodes childElement ***
result : 51.991075[ms]
.
*** prototype.js down + next ***
result : 440.966739[ms]
.
finish!

childNodesの方が速い。まぁ、あんまり変わらないけど、予想と違うぞ?つーか、downとnextの組み合わせ遅すぎ。
ついでにIE6でも見てみる。

preparing ...
let's go!
.
*** initialize ***
result : 67827.967579[ms]
.
*** firstChild + nextSibling childElements ***
result : 1151.967579[ms]
.
*** childNodes childElements ***
result : 14900.967579[ms]
.
*** firstChild + nextSibling childElement ***
result : 59.089179[ms]
.
*** childNodes childElement ***
result : 3354.967579[ms]
.
*** prototype.js down + next ***
result : 22592.967579[ms]
.
finish!

うーん。洒落になってねぇ。ブラウザが死んだかと思たよ。。。childNodes重いっす。。。まぁ、線形探索っぽくchildNodesのitemを見て回っているので、遅くなることはしょうがないのだけど、なんとかならんかな。。。textNodeをすっ飛ばさなくてもいいのだったら、childNodesでindex指定で一発で取れて速そうだけど、今回の目的のelementNodeだけが欲しいならダメだね。こりゃ。

というわけで、firefoxでは、少しだけchildNodesの方が速かったのだけど、IE6のためにfirstChildとnextSiblingを採用するということになりました。。。つーか、downとnextはさらに使いものにならん。。。

prototype.jsのcleanWhitespaceなんかを見てみると、firstChildからnextSiblingを取っているのは理由があったのね。

Leave a comment

Bloglines feedburner