Spring Framework
- Executor(SimpleAsyncTaskExecutor, ThreadPoolTaskExecutor) [2014-03-27]
- SpringAOPベンチマーク(Java Dynamic Proxy, CGLIB) [2013-08-01]
- Springの性能改善 [2014-04-09]
Executor(SimpleAsyncTaskExecutor, ThreadPoolTaskExecutor)
Springの非同期はデフォルト SimpleAsyncTaskExecutor が使用される。
pool-size等の指定があれば ThreadPoolTaskExecutor が使用される。
ThreadPoolTaskExecutorを使用する場合、PoolSizeとMaxPoolSize、TaskQueueCapacityの関係に注意して見積もる。
PoolSizeが拡張される条件は、TaskQueueCapacityがいっぱいになること。
そのため、特に意識しないのであれば、PoolSize=MaxPoolSizeに指定するのが最適。
TaskQueueCapacityは、キュー待ち領域で、FIFO(LinkedBlockingQueue)。適切な値を指定する。
# applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" ・・・省略 http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <-- 非同期アノテーションを有効にする --> <task:annotation-driven executor="myExecutorSample"/> <-- SimpleAsyncTaskExecutor --> <task:executor id="myExecutorSample" /> <-- ThreadPoolTaskExecutor --> <-- <task:executor id="myExecutorSample" pool-size="50" queue-capacity="100" /> --> <-- <task:executor id="myExecutorSample" pool-size="20-50" queue-capacity="100" /> --> <bean id="AsyncSample" class="fomsan.co.jp.example.async.AsyncImple" /> </beans>
# interface例 package fomsan.co.jp.example.async; import org.springframework.scheduling.annotation.Async; public interface IAsync { /** @Asyncアノテーションをつけるだけ */ @Async abstract void Print(int i, String parrent); }
SpringAOPベンチマーク(Java Dynamic Proxy, CGLIB)
Spring AOPで選択できるAOPの仕組みとしては"Java Dynamic Proxy"と"CGLIB"があり、
対象がインターフェースを実装している場合は "Java Dynamic Proxy"、
インターフェースを実装していない場合は "CGLIB Proxy" と、使い分ける。
以下、SpringAOPのJava Dynamic ProxyとCGLIBのベンチマーク。
Java Dynamic Proxy は安定した性能だが、
CGLIB は Singleton の場合は早いが、Prototype になると性能が落ちている。
まとめると、初期化と実行は Java Dynamic Proxy の方が速いが、"singleton"実行であれば CGLIB のほうが速い。
但し、処理時間も1回当たりの差はナノ秒単位なのであまり目くじら立てるほどでもない
●SpringのAOPベンチマーク(Java Dynamic Proxy, CGLIB) ■1Bean生成×10000回 ◇Java Dynamic Proxy singleton:1703ms prototype:1813ms ◇CGLIB singleton:1625ms prototype:1907ms // 以下Beanを10000回生成する(getBean) *singletonはsocpe="singleton" <bean id="service1" class="fomsan.ne.jp.di.ServiceImpl" scope="prototype" /> ■5000Bean生成×1回 ◇Java Dynamic Proxy singleton:937ms prototype:953ms ◇CGLIB singleton:875ms prototype:1079ms // 以下Beanをそれぞれ1回生成する(getBean) *singletonはsocpe="singleton" <bean id="service1" class="fomsan.ne.jp.di.ServiceImpl" scope="prototype" /> <bean id="service2" class="fomsan.ne.jp.di.ServiceImpl" scope="prototype" /> ・・・省略 <bean id="service5000" class="fomsan.ne.jp.di.ServiceImpl" scope="prototype" /> ●getBeanとAutowiredのベンチマーク ■呼び元Singleton→Autowired(singleton)(5000Bean生成×1回) ◇Java Dynamic Proxy ・Autowiredアノテーション singleton:1828ms prototype:1719ms ・getBean(String) singleton:1703ms prototype:1812ms ◇CGLIB ・Autowiredアノテーション singleton:1719ms prototype:1859ms ・getBean(String) singleton:1734ms prototype:1797ms ■呼び元Singleton→Autowired(prototype)(5000Bean生成×1回) ◇Java Dynamic Proxy ・Autowiredアノテーション singleton:1944ms prototype:1969ms ・getBean(String) singleton:1875ms prototype:1890ms ◇CGLIB ・Autowiredアノテーション singleton:2000ms prototype:2125ms ・getBean(String) singleton:1844ms prototype:1984ms ●AOPのサンプル [applicationContext.xml] <!-- 対象メソッド終了後にログを出すだけ --> <!-- proxy-target-classが"true"がCGLIB --> <aop:config proxy-target-class="false"> <aop:aspect id="sample" ref="myMethodIntercepor"> <aop:pointcut id="pointcut" expression="execution(* fomsan.ne.jp.di..*.*(..))" /> <aop:after pointcut-ref="pointcut" method="sampleMethod" /> </aop:aspect> </aop:config> <bean id="myMethodIntercepor" class="fomsan.ne.jp.aop.MyMethodInterceptor" /> <!-- 対象メソッドの呼び出し後・呼び出し前にログを出すだけ --> <!-- proxy-target-classが"true"がCGLIB --> <aop:config proxy-target-class="false"> <aop:advisor pointcut="execution(* fomsan.ne.jp.di..*.*(..))" advice-ref="myLogInterceptor" /> </aop:config> <bean id="myLogInterceptor" class="fomsan.ne.jp.aop.MyLogInterceptor"> <property name="useDynamicLogger" value="true" /> </bean> ●getBeanとAutowiredのサンプル [Java] // getBean時は以下アノテーションをコメントとする @Autowired @Qualifier(value = "SubServiceImpl") private IService iserve; @Override public void sample(ApplicationContext context) { LOG.info("test ok"); // getBean時は以下を有効とする // iserve = (IService) context.getBean("SubServiceImpl"); iserve.sample(context); } [applicationContext.xml] <bean id="SubServiceImpl" class="fomsan.ne.jp.di.sub.SubServiceImpl" scope="prototype" />
Springの性能改善(チューニング)
Springのメリットは何より認知度で、技術者も多いため、皆知っているから大丈夫だろう」という安心感がある。
しかし、その安心とは裏腹に「性能を意識した設計・製造」が出来ていない現場も多く感じる。
大規模なシステムほど、最終的にDIやAOPのコストも無視できないものになってくる。
そのためにも、個人的にはSeaser2をお勧めしたいのだが・・
- バージョンアップを検討する
-
バージョンが古い場合は、バージョンアップを真っ先に検討する
特に2.5では大幅な性能改善が行われている。
また、よくSeaser2と比較されているが、比較対象のSpringのバージョンが古い場合もあるため注意する。 -
例えば、以下資料は2006年に作成されているもので、Springのバージョンは2.0のため比較対象としては古い。
パフォーマンス徹底比較 Seasar2 vs Spring
それでもSeaser2の方が早いと思う(実際に使用してみて)
- コンポネント設計はシングルトン(singleton)パターンとする
- 共通コンポネントは、scopeを"singleton"で設計する。
-
共通な箇所ほど、scopeを"prototype"にしがち。
"prototype"は、コンポネント取得の都度インスタンス生成やAOP折り込みコストが発生する。
そのため、なるべくシングルトンで耐えられる設計とする(排他コストが大きくなってしまっては元も子もないが・・)。
- Transaction管理を意識する
-
「参照のみ」には"readOnly=true"を設定し、トランザクションの制御を適切に行う。
(トランザクションを無駄に作らない、トランザクションを無駄にネストしない)
また、DBコネクションのプール管理は必ず行い、可能であれば"Statement"もプール管理すること。 -
REQUIRED(SUPPORTS), REQUIRED_NEW, NOT_SUPPORTEDを意識した設計・製造をする。
コネクション・トランザクション生成のコスト自体も大きく、DBサーバへの負荷・リソース不足の原因にもなる。
- なんでもかんでも DI (Dependency Injection) ・AOP (Aspect Oriented Programming) していないか
-
コンテキストファイル(applicationContext.xml)が肥大化している場合は大体これ。
DI対象のコンポネントは"依存性をなくしたい"ものだけにする。
また、DIによる動的な引数(パラメータ)渡しもなるべく避ける。 -
DIやAOPしている箇所は、可読性が悪くなる(コードが追いづらい)傾向にあり、
可読性が悪くなると、保守性も悪くなる。
また、処理内容はDIやAOPが行われるため、最終的には性能に大きく影響してくる。
特に、DIやAOPによる動的な引数(パラメータ)渡しは、コストが大きい。