[mayaa-user:1035] Re: XPathMatchesInjectionResolverの高速化について

Susumu ISHIGAMI [E-MAIL ADDRESS DELETED]
2014年 4月 26日 (土) 20:38:11 JST


石上です。

XPathInjectionResolverの改善を私の扱っている案件で行いました。
extendによる拡張のみで対応ができたため、Mayaa本体のソースコードに手を加えておりません。

下記1から4までの対応によって、
私のPCで単体でビルドに5秒以上かかっていたテンプレートファイルのビルド時間が、
最短3.75秒に短縮されました。

これらのうち、3,4の対応を本体に取り込むことは難しいと思いますが、
1,2の対策はmayaa本体の改善に寄与する可能性がありますので、
もしよろしければ、本体に取り込む可能性をご検討いただければと思います。

また、本件のテスト中、別の問題として、ビルド時間にヒープに残サイズが影響することがわかりました。
GCを強制実行すると、直後のビルド時間が最短に近づきます。FullGC直前では12秒かかりました。
これはJavaVMのパラメータによって動作が変わる可能性がありますが、
何か改善の余地があるかもしれませんので、私の方で何かわかれば報告致します。
この問題があるため、速度向上に最も寄与したのが1から4のどれなのかわからない状態となってしまいました。

以下、詳細をご説明します。どなたかのご参考になれば幸いです。
ソースコードはタブの代わりに全角スペースを使用しています。

1.org.seasar.mayaa.impl.builder.injection.XPathMatchesInjectionResolver.getNode(SpecificationNode,
InjectionChain)の冒頭で、
 不要なSpecificationNodeに対する処理をスキップ

 ログを仕掛けてみると、第一パラメータに、QName = http://mayaa.seasar.org/characters など、私の理解では
 XPathで処理する必要がないと思われるNodeが入ってきていました。
 そこで、下記のif文を追加し、後続の処理を行わないようにしました。

 // 処理不要なnodeを除外
 if (qName.equals(QM_COMMENT)
   || qName.equals(QM_CHARACTERS)
   || qName.equals(QM_DOCTYPE)
   || qName.equals(QM_CDATA)) {
  return chain.getNode(original);
 }

 この対策はEqualsIDInjectionResolverに対しても有効でしたので、私のところではこちらにも追記しました。
 ただ、EqualsIDInjectionResolverの場合はすぐにgetIDの結果で弾かれてしまうため、効果は小さいと思われます。

2.getXPathNodes(mayaa, injectNodes);の結果をキャッシュしました。
 もともとgetXPathNodesを呼んでいる箇所をgetXPathNodesWithCacheに変更し、
 下記のように実装しました。

 @SuppressWarnings({ "rawtypes", "unchecked" })
 private void getXPathNodesWithCache(SpecificationNode mayaa, List
specificationNodes) {
  List injectedNodes = getXPathNodesFromCache(mayaa);
  specificationNodes.addAll(injectedNodes);
  return;
 }

 @SuppressWarnings("rawtypes")
 private List getXPathNodesFromCache(SpecificationNode mayaa) {
  Map<SpecificationNode, List> cache =
MyTemplateBuilderImpl.getXPathNodesCacheOfThisRequest();
  List injectedNodes = cache.get(mayaa);
  if (injectedNodes == null) {
   injectedNodes = new ArrayList();
   getXPathNodes(mayaa, injectedNodes);
   cache.put(mayaa, injectedNodes);
  }
  return injectedNodes;
 }

 なお、MyTemplateBuilderImpl.getXPathNodesCacheOfThisRequest();は独自で作成したTemplateBuilderImplで、以下のように実装していています。

 @Override
 public void build(Specification specification) {
  @SuppressWarnings("rawtypes")
  Map<SpecificationNode, List> cache = new HashMap<SpecificationNode, List>();
  CycleUtil.getRequestScope().setAttribute("xPathNodesCache", cache);
  super.build(specification);
  cache.clear();
  CycleUtil.getRequestScope().removeAttribute("xPathNodesCache");
 }

 @SuppressWarnings({ "unchecked", "rawtypes" })
 public static Map<SpecificationNode, List> getXPathNodesCacheOfThisRequest() {
  return (Map<SpecificationNode, List>)
CycleUtil.getRequestScope().getAttribute("xPathNodesCache");
 }

 レイジーロードにしても良かったのですが、キャッシュの寿命を自分で管理したかったためこのようにしました。

3.getXPathNodesの実装を変更し不要な要素を辿らないようにしました。

 @SuppressWarnings({ "rawtypes", "unchecked" })
 @Override
 protected void getXPathNodes(SpecificationNode node, List specificationNodes) {
  if (node == null) {
   throw new IllegalArgumentException();
  }
        for (Iterator it = node.iterateChildNode(); it.hasNext();) {
            SpecificationNode child = (SpecificationNode) it.next();
            if (QM_MAYAA.equals(node.getQName())) {
             String xpath = SpecificationUtil.getAttributeValue(child,
QM_XPATH);
             if (StringUtil.hasValue(xpath)) {
              specificationNodes.add(child);
             }
            } else {
             getXPathNodes(child, specificationNodes);
            }
        }
 }
 今のロジックでは、mayaaノード直下以外にxpathを記載した場合エラーメッセージを出力することができますが、
 その機能がなくなる代わりに、mayaaファイル内でたどるノード数を減らしました。

4.XPathUtil.matchesを使用する手前で独自の条件分岐を行い、可能な限りxpath処理を行わないようにしました。
 これは、html/head/titleなどほとんど決め打ちであることが多かったためです。
 パフォーマンスを気にする状況で、一部のタグを決め打ちで書き換えたい場合は、
 XPathを使わずに、このように独自のInjectionResolverを作成するアプローチもあり得ると思います。

2014年3月16日 19:21 suga <ko.suga @ gmail.com>:
> suga です。
>
>> mayaaファイル毎に、結果のリストをキャッシュして、
>> 2回目以降はキャッシュを利用するようにすれば高速化出来るのではないかと思われますが、
>> そのようなアプローチは可能でしょうか?
>
> XPath の処理回数を減らさないとあまり効果は無いかもしれません。
>
> MayaaのビルドはHTML側の要素を起点として.mayaa側を参照しに行くのですが、
> XPathとしてはその逆が本来のあり方ですので、処理速度を上げるには
>
> - XPathMatchesInjectionResolver 側のインスタンスでテンプレートをスキャン、
> - その結果に比較しに来たノードが含まれているかチェック
>
> という方向で作り替える必要があるかと思います。
>
> --
> suga ( ko.suga @ gmail.com )
>
>
> 2014-03-15 20:05 GMT+09:00 Susumu ISHIGAMI <susumu.ishigami @ gmail.com>:
>> 石上です。
>>
>> mayaaを使用させて頂いているWebサイトで、
>> 複雑なテンプレートを扱うとテンプレートのビルドが遅くなる状況が発生しています。
>> プロファイラによって調べてみると下記の箇所が原因の一つではないかと思われてます。
>>
>> XPathMatchesInjectionResolver.getXPathNodes(SpecificationNode, List)
>>
>> 実際に、
>> org.seasar.mayaa.provider.ServiceProvider
>> を書き換えて
>> <resolver class="org.seasar.mayaa.impl.builder.injection.XPathMatchesInjectionResolver"/>
>> をコメントアウトすると、ビルド時間が1/10に短縮されました。
>>
>> しかし実際にはmayaaファイルでm:xpathを使用してしまっておりますので、
>> 今更XPathMatchesInjectionResolverをやめるという選択肢はなく、
>> XPathMatchesInjectionResolverをある程度高速化出来ないかと検討しています。
>> (その際、多少の機能制限があってもしかたがないと思っています)
>>
>> getXPathNodes()
>> の実装を読ませていただきますと、
>> mayaaファイル全体をなめて再帰的に
>> getXPathNodes()
>> を呼び出しています。
>>
>> この呼出が、getNode()から呼ばれていますので、
>> テンプレートの全部の要素からgetXPathNodes()
>> の再帰ループが呼ばれているように見受けられます。
>>
>> mayaaファイル全体をなめるのであれば、
>> mayaaファイル毎に、結果のリストをキャッシュして、
>> 2回目以降はキャッシュを利用するようにすれば高速化出来るのではないかと思われますが、
>> そのようなアプローチは可能でしょうか?
>> _______________________________________________
>> mayaa-user mailing list
>> mayaa-user @ ml.seasar.org
>> https://ml.seasar.org/mailman/listinfo/mayaa-user
> _______________________________________________
> mayaa-user mailing list
> mayaa-user @ ml.seasar.org
> https://ml.seasar.org/mailman/listinfo/mayaa-user



-- 
Susumu ISHIGAMI
susumu.ishigami @ gmail.com


mayaa-user メーリングリストの案内