このブログでは、記事中のプログラムの文法ハイライトに、 Syntax Highlighter and Code Colorizer for WordPress というプラグインを使用しています。 JavaScript と CSS で、プログラムを見易くしてくれる素敵なプラグインなのですが、 JavaScript ファイルだけで 1 度に 18 ものファイルを読むのが気になるところです。

外部のリソースが増えると、当然 HTTP リクエストも増え、ページ全体の表示時間に影響が出ます。

というわけで、以下のようなスクリプトを用意しました。

使い方

まずは、上記 Ruby スクリプトを syntax-highlighter-minimizer.rb といった適当な名前で保存します。

このスクリプトの動作には、 JSMin という Rubygems が必要なので、インストールします。

$ gem install jsmin --no-rdoc --no-ri

スクリプトは以下のように使用します。

$ ruby syntax-highlighter-minimizer.rb /path/to/syntax-highlighter-and-code-prettifier/script

プログラムの引数として、 sh***.js といったファイルの含まれるディレクトリのパスを渡して実行します。すると、ディレクトリ内に以下のようなファイルが保存されます。

  • syntax-highlighter-all.js … sh***.js をすべて結合したもの
  • syntax-highlighter-all-min.js … ***.js をすべて結合し、難読化したもの

仕上げとして、 JavaScript ファイルの読み込み部分を書き換え、ファイルを結合・難読化したものと置き換えます。

./wp-content/plugins/syntax-highlighter-and-code-prettifier/syntax-highlighter.php

18 もあった JavaScript ファイルがひとつにまとまりました !

難読化とは

文字通りに言えば、プログラムを読みにくくすることですが、実際はソースコードの圧縮が目的です。インデント等を極力少なくすることで、圧縮されます。

どれぐらいの効果があるか

以下のようなページを作って測定してみました。

  • default.html … 元の 18 の JavaScript が読み込まれる
  • all.html … 18 の JavaScript を 1 つのファイルに結合したものが読み込まれる
  • all-min.html … 1 つのファイルに結合したのち、難読化したものが読み込まれる

いずれも、 script タグにより JavaScript が読み込まれるだけのシンプルなページです。 FireFox で Ctrl + F5 の強制更新で、 onload イベントの発生までの時間を計測しています。

default.html all.html all-min.html
1 379ms 140ms 180ms
2 375ms 148ms 168ms
3 556ms 131ms 133ms
4 365ms 135ms 124ms
5 370ms 146ms 137ms
6 363ms 128ms 133ms
7 370ms 257ms 132ms
8 397ms 122ms 133ms
9 411ms 127ms 136ms
10 362ms 135ms 135ms
Avg. 394.8ms 146.9ms 141.1ms

難読化による効果は誤差程度に止まりましたが、 JavaScript ファイルをまとめることは、絶大な効果があるようです。今回の例でいえば、 2.8 倍近い高速化が実現できました。

ただし、実際のウェブサイトは、 JavaScript 以外にもたくさんの要素を含み、キャッシュ等の要因も絡むため、ここまでのインパクトは無いと思いますが、ページの表示時間が気になるときは、是非検討してみるべきでしょう。

今後の予定

今回は特定のプラグインに標的を絞りましたが、 WordPress といった CMS を使用する場合、プラグインで JavaScript や CSS の数がもの凄いことになっていた ! というのはよくあると思います。それらを 1 つ 1 つ潰していくことで、案外バカにできない効果が期待できそうです。

あと、 JavaScript ファイルを減らそう ! とかいいながら、この記事には GitHub の Gist が 2 つも貼り付けられています。これらは JavaScript の外部リソースによるものなので、ページ全体の読み込み時間に影響します。ページの読み込み時に読み込むのではなく、「このコードは読んでみたいな」と思ってクリックするまで、読み込みを遅延させる、という仕組みもアリかもしれません。

参考文献

[エラー: asin:487311361X というアイテムは見つかりませんでした]

Yahoo ! のフロントエンド最適化チームによるノウハウ集で、 JavaScript の結合・難読化についてはほとんどこの書籍に書いてあります。

その他にも様々なテクニックが紹介されていますが、その中でも特に敷居が低いのが、この JavaScript についての最適化です。

是非試してみてはいかがでしょうか。

, ,

例えば memoize しながらフィボナッチ数列を求める場合。

class Fibonacci
  def initialize
    @memo = [0, 1]
  end

  def fibonacci(n)
    @memo[n] || @memo[n] = fibonacci(n - 2) + fibonacci(n - 1)
    @memo[n] # この行は省略しても結果は同じ
  end
end

fib = Fibonacci.new
10000.times { |n| fib.fibonacci(n) }

これで何が嬉しいのかというと、プロファイリングしてみるとこんな結果が現れます。

fibonacci メソッドの最後の行を省略しなかった場合

$ time ruby -rprofile fibonacci.rb
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 67.92     3.24      3.24    29996     0.11     0.21  Fibonacci#fibonacci
 16.98     4.05      0.81    59992     0.01     0.01  Array#[]
  6.29     4.35      0.30        1   300.00  4770.00  Integer#times
  3.56     4.52      0.17    19996     0.01     0.01  Fixnum#-
  2.73     4.65      0.13     9954     0.01     0.01  Bignum#+
  2.52     4.77      0.12     9998     0.01     0.01  Array#[]=
  0.00     4.77      0.00        2     0.00     0.00  Module#method_added
  0.00     4.77      0.00        1     0.00     0.00  Fibonacci#initialize
  0.00     4.77      0.00       45     0.00     0.00  Fixnum#+
  0.00     4.77      0.00        1     0.00     0.00  Bignum#coerce
  0.00     4.77      0.00        1     0.00     0.00  Class#inherited
  0.00     4.77      0.00        1     0.00     0.00  Class#new
  0.00     4.77      0.00        1     0.00  4770.00  #toplevel

real    0m6.005s
user    0m4.776s
sys     0m1.184s

fibonacci メソッドの最後の行を省略した場合

$ time ruby -rprofile fibonacci_fast.rb
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 66.39     2.39      2.39    29996     0.08     0.14  Fibonacci#fibonacci
  8.06     2.68      0.29    29996     0.01     0.01  Array#[]
  8.06     2.97      0.29    19996     0.01     0.01  Fixnum#-
  6.94     3.22      0.25        1   250.00  3600.00  Integer#times
  5.56     3.42      0.20     9998     0.02     0.02  Array#[]=
  5.00     3.60      0.18     9954     0.02     0.02  Bignum#+
  0.00     3.60      0.00        1     0.00     0.00  Fibonacci#initialize
  0.00     3.60      0.00        1     0.00     0.00  Class#inherited
  0.00     3.60      0.00        2     0.00     0.00  Module#method_added
  0.00     3.60      0.00        1     0.00     0.00  Class#new
  0.00     3.60      0.00       45     0.00     0.00  Fixnum#+
  0.00     3.60      0.00        1     0.00     0.00  Bignum#coerce
  0.00     3.60      0.00        1     0.00  3600.00  #toplevel

real    0m4.516s
user    0m3.600s
sys     0m0.904s

実行時間が約 25% 短縮されました。

注目するポイントは 2 行目の Array#[] メソッド、つまり配列から値を引き出す処理の回数が半分になっているというところ。どうしてこうなるかは、コードの意味を考えながら読めばすぐにわかると思います。ただ、こういう最適化によって、有意な効果を得られるケースがあるのかは疑問ですが・・・。

でもせっかくなので、代入の返り値はどうなってるのか、言語ごとに調べてみました。

Ruby

p (foo = "bar")
# => "bar"

JavaScript (Rhino)

var foo;
print(foo = "bar");
// => bar

Perl

my $foo;
print $foo = "bar";
# => bar

PHP

var_dump($foo = "bar");
// => string(3) "bar"

MySQL (これはちょっと番外編?)

mysql> SELECT @today := CURDATE();
+---------------------+
| @today := CURDATE() |
+---------------------+
| 2010-02-09          |
+---------------------+
1 row in set (0.00 sec)

と、ここまでは、どれも代入値が返ってくるようですが・・・

Python

print(foo = "bar")
# => SyntaxError: invalid syntax

Python では Syntax Error となります。

これを知って何の役に立つのかイマイチわかりませんが、こういうことになっていますよ、ということで。

,