jQueryとDOM操作の最適化

前回同様、O’REILLYの「JavaScriptグラフィックス」1章効率化についてメモ。

キモは

  1. 「要素が見つかったら極力再検索しない」
  2. 「要素の追加は、文字列で一括して行う」

1.要素の検索について

$('#elem1').css('color' , '#ff0000');//赤
$('#elem1').css('color' , '#00FF00');//緑
$('#elem1').css('color' , '#0000FF');//青
$('#elem1').css('left' , '100px');//x座標を動かす

このサンプルではelem1を毎回検索しているので、効率が悪い。
そこで検索結果を変数に保存する。

$alien=$('#elem1');

となる。

※ここまでは想像していたのだが、この章の説明は検索範囲の指定まで踏み込んでいる。
曰く「jQueryが検索する要素の範囲を指定したほうが速い」。
$alien=$(‘#element1′ , container);//特定のDOM要素から検索する
個人的にはあまり使わないような気がするが一応覚えておく。

まだjQueryのcss()関数が残っている。これも変数に格納。

var elemStyle=$('#elem1')[0].style;

そしてcssスタイルを設定する。

elemStyle.color='#ff0000';
elemStyle.color='#00ff00';
elemStyle.color='#0000ff';
elemStyle.left='100px';

(なぜかelemStyle.colorをアラート表示できない)

2.要素の追加は文字列で一括して行う
DOMへの要素挿入は遅いので、

文字列+=追加文字列
で文字列を作成し、
$elem.append(文字列);
で一気に追加する。

これらを踏まえたサンプル。
http://nouv.biz/labo/20120908_jQuery_speed/ex0100_css.html

css操作はcssが設定されていないと動作しないので注意。コードは以下のとおり。

[html]
<div id=”element1”><div>
[css]
#element1{
	position:absolute;
	left:0;
	top:0;
	font-size:80px;
	color: #000000;
}
[JavaScript]
var $elem;//jQueryから取得
var elemStyle;//取得した要素のスタイル
var divs='';//追加する要素

//検索し、変数に格納 idを取得しているのに、配列のようなものになるらしい
$elem=$('#element1');
//$elem[0].addEventListener("click" , onClicked , false);
AddEvent.addEvent($elem[0] , "click" , onClicked);//オリジナルクラス

//変数からstyleを格納
//var elemStyle=$elem.style;
//Debugger.log(elemStyle);//undefinedになる
var elemStyle=$elem[0].style;//なぜか添字0をつけなければならない。
Debugger.log(elemStyle);

//makeDivs
//makeDivs
for(var i=0 ; i divs+=''//文字列追加省略
}

function onClicked(event){
Debugger.log("clicked");
elemStyle.color="#ff0000";
elemStyle.left="50px";
elemStyle.top="50px";
$elem.append(divs);
}

ルックアップテーブルとビット演算

O’REILLYの「JavaScriptグラフィックス」1章効率化についてメモ。

O’REILLYの書籍サイトではサンプルとして以下が掲載されている。
http://www.oreilly.co.jp/pub/9784873115283/ch01/ex0100.html
サインカーブを描くシンプルなもの。

特徴は

  • Canvasを用いず、ブロック要素(div)をhtml側に記述しcssで幅・高さを指定
  • ブロック要素内に幅1pxの縦線を幅の分の数描画する(この場合480本)
  • 描画の開始位置と高さはJavaScriptで変動させアニメーション化する

効率化のメインは

  1. ルックアップテーブル
  2. ビット演算

の2つ。
1つ目のルックアップテーブルは、都度sinを計算するのではなく、前もってsinを計算し、配列に格納することにより速度向上を図るということ。wonderFL(flash)でもスクリプトで絵柄をビットマップ描画し、それを配列に格納することで超高速化していたサンプルがあった(http://wonderfl.net/c/c0Gi)。

一方、2つ目のビット演算は少々わかりづらい。
なんの前置きも無く、

        drawGraph(x * 50, 32 - (sinTable[(x * 20) & 4095] * 16),
                  50 - (sinTable[(x * 10) & 4095] * 20));

とか

bars[i].style.top = 160 - height + 
                 sinTable[(ang + (i * freq)) & 4095] * height + 'px';

など唐突に4095という数字が出てくる。
結局
配列の生成数4096は16の3乗、0×1000
配列範囲(添字)の最大値 4095は16の3乗-1    0xFFF
ということにさえ気づければなんということは無かったのだけれど。

このサンプルでは360度(Math.2PI)を4096分割し、そのsin値を配列に格納している。
タイマー内の変数xを単純な足し算(x++)でどんどん増やしているため、このxから0~4095(配列範囲)の添字を算出する必要がある。そこでビット単位のAND(&)計算を用い
0×1000 (=4096)
0x  FFF(=4095)        &
———————————–
0X  000(=0 配列の先頭の添字)
などと計算し、範囲溢れを防ぎつつ添字を求めている。
私はこういう場合、余り(%、MOD)を求めて算出するのが通常だった。
余りで計算する場合、配列数(length=4096)を用いる。
上記の例だと
4096%4096=0 余り0 配列の先頭
4097&4096=0 余り1 配列の2つ目
となる。

ところがこの「余り」の計算は四則演算の中で最も遅いらしい
(参考にさせていただきました http://ufcpp.net/study/algorithm/col_circular.html) 。

このサンプルでは高速化のため、これをAND演算に切り替えている。
なるほど。
確かにカーブを書く都度「余り」計算を行なっているので計算回数も多い。ここの高速化は全体への影響も大きいだろう。
これを流用したサンプルはまた後日。