設計パターンを活用したパフォーマンス最適化の事例分析

設計パターンは、よく使われる開発技法を体系化したものです。これにより、エンジニア同士の技術的コミュニケーションがより効率的になります。例えば、I/Oモジュールにおけるデコレーターパターンの採用は、コード構造を理解する上で大きなヒントになります。

多くの設計パターンはパフォーマンス向上に直接的な影響を与えるわけではなく、コード構成の方法論としての側面が強いです。本記事では、パフォーマンスに影響を与える設計パターンについて具体例を交えて解説します。代理パターン、シングルトンパターン、フライウェイトパターン、プロトタイプパターンなど、それぞれの特徴と適用ケースを確認します。

ダイナミックプロキシの遅延原因特定手法

SpringフレームワークではCGLIBを用いたプロキシ生成が広く採用されています。複雑なプロジェクトでは多くのAOP処理が存在し、権限チェックやログ記録などの切片処理が行われます。以下に、Arthasツールを用いた性能ボトルネックの特定方法を紹介します。

@Component 
public class SampleBean { 
    public void execute() { 
        System.out.println("#######################"); 
    } 
}
@Aspect 
@Component 
public class LoggingAspect { 
    @Pointcut("execution(* com.example.demo.SampleBean.*(..))") 
    public void definePointcut() { 
    }  
    @Before("definePointcut()") 
    public void beforeAdvice() { 
        System.out.println("Logging started"); 
        try { 
            Thread.sleep(1000); 
        } catch (InterruptedException e) { 
            throw new IllegalStateException(); 
        } 
    } 
}
@Controller 
public class AopTestController { 
    @Autowired 
    private SampleBean sampleBean;  
    @ResponseBody 
    @GetMapping("/aop") 
    public String testAop() { 
        long startTime = System.currentTimeMillis(); 
        sampleBean.execute(); 
        long duration = System.currentTimeMillis() - startTime; 
        String className = sampleBean.getClass().toString(); 
        return className + " | " + duration; 
    } 
}

実行結果は以下の通りで、CGLIBによるプロキシ生成が確認できます。

class com.example.demo.SampleBean$$EnhancerBySpringCGLIB$$a5d91535 | 1023

Arthasを使用した解析手順は以下の通りです:

  1. アプリケーションを選択して分析画面へ移動
  2. trace com.example.demo.SampleBean executeコマンドを実行
  3. /aopエンドポイントを呼び出して処理トレースを取得

代理パターンの比較

Javaにおける動的プロキシ実装にはJDKとCGLIBの2種類があります。

実装方法 対象 主要クラス
JDK インターフェース InvocationHandler, Proxy
CGLIB クラス MethodInterceptor, Enhancer

JMHベンチマーク結果(JDK1.8環境):

Benchmark              Mode  Cnt      Score      Error   Units 
ProxyBenchmark.cglib  thrpt   10  78499.580 ± 1771.148  ops/ms 
ProxyBenchmark.jdk    thrpt   10  88948.858 ±  814.360  ops/ms 

プロキシ作成処理のベンチマーク:

Benchmark                    Mode  Cnt      Score      Error   Units 
ProxyCreateBenchmark.cglib  thrpt   10   7281.487 ± 1339.779  ops/ms 
ProxyCreateBenchmark.jdk    thrpt   10  15612.467 ±  268.362  ops/ms 

SpringがCGLIBを選択した主な理由は、インターフェース不要なクラスレベルのプロキシ生成が可能であるためです。

シングルトンパターン

Springコンテナでは@Scopeアノテーションでスコープを指定できます。シングルトンの実装方法として、ダブルチェックロックとenumを用いた手法があります。

public class SingletonManager { 
    private static volatile SingletonManager instance;  

    public static SingletonManager getInstance() { 
        if (instance == null) { 
            synchronized (SingletonManager.class) { 
                if (instance == null) { 
                    instance = new SingletonManager(); 
                } 
            } 
        } 
        return instance; 
    } 
}

このダブルチェック方式は線程安全性を保証しますが、volatileキーワードの使用や同期の必要性など、注意点が多くなります。現在ではenumを用いた実装が推奨されています。

public class EnumSingleton { 
    private EnumSingleton() { 
    } 
    public static EnumSingleton getInstance() { 
        return Holder.HOLDER.instance; 
    } 
    private enum Holder { 
        HOLDER; 
        private final EnumSingleton instance; 
        Holder() { 
            instance = new EnumSingleton(); 
        } 
    } 
}

フライウェイトパターン

オブジェクトの共有を目的とした設計パターンで、インスタンスの再利用を最大化します。以下は戦略パターンの例ですが、オブジェクト共有の観点からはフライウェイトパターンとみなせます。

Map<String, Strategy> strategyMap = new HashMap<>(); 
strategyMap.put("alpha", new AlphaStrategy()); 
strategyMap.put("beta", new BetaStrategy()); 

プロトタイプパターン

複製作成を基本とする設計パターンで、Javaのclone()メソッドが代表的です。ただし浅いコピーしか行えず、深さのコピーにはシリアライズなど他の手段が必要です。

タグ: Spring AOP CGLIB シングルトン フライウェイト

7月31日 22:28 投稿