何がやりたいか? †
- ある引数に対して決まった結果を返すメソッドがある場合、結果をキャッシュしておき、処理を行わずに値を返すようにすれば高速化を図れる
- このとき、AspectJ の @Around アドバイスを使ってメソッドの実行を乗っ取れば、元のプログラムを変更しなくてもよい
例題 †
- フィボナッチ数を求めるプログラムを作る
- 定義
F(n) = F(n-1) + F(n-2) F(0) = 0 F(1) = 1
- フィボナッチ数列
0,1,1,2,3,5,8,13,21,34,55,89,...
- 定義
- フィボナッチ数を F(0) から F(49) まで求めるプログラムを定義そのまま馬鹿正直に作る
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
- | - - | | | | | | | | | ! ! | - - | ! - | ! | ! | !
package com.snail.exam.aj.fibonacci; public class Fibonacci { public static void main(String[] args){ for( int n = 0 ; n < 50 ; n++ ){ double now = System.currentTimeMillis() * 1000000.0 + System.nanoTime(); long fn = fibonacci(n); double end = System.currentTimeMillis() * 1000000.0 + System.nanoTime(); System.out.println( n + ",F(" + n + ")=" + fn + "," + (end - now)/1000000000.0); } } public static long fibonacci(int n){ if( n == 0 ){ return 0; } if( n == 1 ){ return 1; } return fibonacci(n-2) + fibonacci(n-1); } }
キャッシング Aspect †
あんまり遅いので、Fibonacci#fibonacci(n) の結果をキャッシングする Aspect を適用する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| - | | - | - | ! ! | - | ! | | - | | | | - | ! | | ! ! | |
- LinkedHashMap? (入れた順番を記憶できるMap) に、(Key,Value)=(引数,計算結果) を記録しておき、処理結果をキャッシュしておく
- キャッシュに載っている引数で Fibonacci#fibonacci(n) が呼ばれた場合は、計算せずにキャッシュの内容を返す
- キャッシュに載っていない場合には計算を行って、計算結果をキャッシュに格納する
- キャッシュの実現方法について、
実行結果 †
- F(20)までならば、Aspect を適用しない方が速い
(キャッシュ機構に払うコスト > 実際に計算するコスト) - しかし、F(49)では、まじめに計算すると 300 秒かかるが、キャッシュAspect を適用すると 0.0001 秒で答えが出る
なんでそうなるのか? †
- キャッシュサイズ (MAX_ENTRIES) が 2 なのになんでこんなに速くなるのか?
- 元のプログラムで F(n) = F(n-2) + F(n-1) となっているのがミソ*1
- たとえば、F(300) を求めるのに必要な、F(299) はキャッシュに載っている F(297) と F(298) の和になっている
- この考えを進めると、前回 2つ分の計算結果を保持しておくことにより、劇的に計算量を減らせることが分かる(ほぼ計算量が平方根になる)