プログラムの高速化については本連載の第13回で紹介しています。プログラム中のどの部分を高速化すべきなのか,どのように効果を測定するのか,そもそも高速化が必要なのかを考察しました。今回は実際に高速化を試みます。

 今回はプログラム高速化の実践編として,ある程度の大きさの具体的なプログラムを段階的に高速化しながら,実践的なテクニックを学ぶことにしましょう。

 例題はマンデルブロ集合*1の計算プログラムです(図1)。マンデルブロ集合は非常に美しい集合ですから,本当はグラフィックス表示が好ましいでしょう*2。しかし,GUIを使うと,特定のGUIライブラリ(グラフィックス・ルーチン)に依存したコードが大量に必要です。今回のテーマは「高速化」ですから,直接関係のないGUIコードはできるだけ省くため,キャラクタ(英文字)で図形を表現することにしました。

1 require 'complex'
2
3 def mandelbrot(cr, ci)
4   limit=95
5   iterations=0
6   c=Complex.new(cr,ci)
7   z=Complex.new(0,0)
8   while iterations max_i
18  print "|"
19  cur_r = min_r
20  while cur_r < max_r
21   ch = 127 - mandelbrot(cur_r, cur_i)
22   printf "%c", ch
23   cur_r += res
24  end
25  print "|n"
26  cur_i -= res
27  end
28 end
29
30 mandel_calc(-2, 1, 1, -1, 0.04)
図1●マンデルブロ集合計算プログラム
ファイル名はmandel1.rb。一切最適化を施していない。

 キャラクタ・ベースの出力にした結果,全体の計算量が減り,測定時間が短くて済むという副作用もありました。高速化のためには頻繁に測定する必要がありますから,1回の実行時間が短いことはメリットです。

 特定のAPI(Application Programming Interface)に依存したGUIベースのプログラムは,APIの変更に弱くなりますが,キャラクタ・ベースなら長期的にも安心です。

プログラムの概要を確認する

 図1はマンデルブロ集合の計算アルゴリズムそのものに集中したコードですから,GUI版に比べてシンプルです*3。参考までにGUI版はコードが102行ありました。

 プログラムの構造は簡単です。15行目から28行目にあるmandel_calcメソッドが指定した範囲のマンデルブロ集合を計算します。今回は美しく見える範囲(座標)として30行目で「(-2,1)-(1,-1)」を指定しました。第5引数は分解能です。一マス(キャラクタ)が0.04に相当します。この数値を大きくすると表示結果は小さくなります。

 mandel_calcメソッドが内部で呼び出しているのが,3行目から13行目のmandelbrotメソッドです。ある1点の状態のみを計算します。

 まずは単純に実行してみましょう。

% ruby mandel1.rb

 マンデルブロ集合をキャラクタで表現したものが表示されます(図2)。キャラクタによってちょっとぼんやりした形で表現されているのが分かるでしょう。

図2●図1の出力結果
図2●図1の出力結果
マンデルブロ集合をキャラクタで表現したもの。

 今度は実行時間を測定してみましょう。前回も説明しましたが,実行時間の測定にはLinuxのtimeコマンドを使います。

% time ruby mandel1.rb > /dev/null
real  0m2.601s
user  0m2.432s
sys   0m0.004s

 これは私のマシン上(1.6GHz動作のPentium M)での実行結果です。他のプロセスの影響による測定誤差を避けるため,10回繰り返して実行し,最も成績の良い値を採用しました。今回測定したいのは計算時間で,出力に必要な時間はむしろ邪魔ですから,出力は/dev/nullデバイスにリダイレクトして捨てています。