AspectJ 実行結果のキャッシング

何がやりたいか?

  • ある引数に対して決まった結果を返すメソッドがある場合、結果をキャッシュしておき、処理を行わずに値を返すようにすれば高速化を図れる
  • このとき、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) まで求めるプログラムを定義そのまま馬鹿正直に作る
    Everything is expanded. Everything is shortened.
      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 を適用する。

Everything is expanded. Everything is shortened.
  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
 
 
 
 
 
 
 
 
 
 
-
|
|
-
|
-
|
!
!
|
-
|
!
|
|
-
|
|
|
|
-
|
!
|
|
!
!
package com.snail.exam.aj.fibonacci;
 
import java.util.LinkedHashMap;
import java.util.Map;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
 
@Aspect
public class FibonacciCache {
  
  private static final int MAX_ENTRIES = 2;
  private static Map<Integer,Long> cache = new LinkedHashMap<Integer,Long>(){
    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer,Long> eldest) {
      return size() > MAX_ENTRIES;
           }
  };
  
  public static int getCacheSize(){
    return cache.size();
  }
  
  @Around("call(long com.snail.exam.aj.fibonacci.Fibonacci.fibonacci(int))")
  public long cacheFibonacci(ProceedingJoinPoint thisJoinPoint) throws Throwable{
    
    Object[] args = thisJoinPoint.getArgs();
    Integer arg = (Integer)args[0];
    
    if(!cache.containsKey(arg)){
      cache.put(arg, (Long)thisJoinPoint.proceed(thisJoinPoint.getArgs()));
    }
    
    return cache.get(arg);
  }
}
  • LinkedHashMap? (入れた順番を記憶できるMap) に、(Key,Value)=(引数,計算結果) を記録しておき、処理結果をキャッシュしておく
    • キャッシュに載っている引数で Fibonacci#fibonacci(n) が呼ばれた場合は、計算せずにキャッシュの内容を返す
    • キャッシュに載っていない場合には計算を行って、計算結果をキャッシュに格納する
  • キャッシュの実現方法について、
    • LinkedHashMap?#removeEldestEntry?() を Override することにより、LinkedHashMap? をキャッシュとして利用できる
    • LinkedHashMap?#removeEldestEntry?() が、true を返すと、最も昔に登録されたエントリが削除される。

実行結果

ajCache3.png
  • 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) の和になっている
      ajCache1.png
    • この考えを進めると、前回 2つ分の計算結果を保持しておくことにより、劇的に計算量を減らせることが分かる(ほぼ計算量が平方根になる)
      ajCache2.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值