posts - 27,  comments - 0,  trackbacks - 0
          點滴悟透設計思想,Tiny模板引擎優化實錄!
          加入框架設計興趣小組:http://bbs.tinygroup.org/group-113-1.html

           

          Tiny模板引擎的實現方式原來是采用的編譯方式,最近發生了一些問題,因此我覺得有必要把編譯方式調整為解釋方式,為此就開始了此次實現活動。

          編譯方式存在的問題

          當時采用編譯方式,主要是考慮到編譯方式在運行時不必再去遍歷語法樹,因此就采用了編譯方式。但是在實際應用當中,出現了如下問題:

          文件路徑沖突的問題

          由于采用的是編譯方式,這個時候就存在在一個選擇,即:Java源代碼落地或不落地的選擇。如果Java文件不落地,則在有問題的時候,如果想要進行代碼調試(雖然這種場景并不多見),那么就沒有源代碼可供調試。如果Java代碼落地,則會存在一個問題,那就是資源文件在磁盤文件中產生沖突的問題。

          同樣的問題對于class文件也存在,如果不落地,那么每次應用重啟動的時候,都要重新編譯這些文件以產生class文件;如果落地,則也會產生沖突的問題。

          當然,Tiny模板引擎通過增加一個配置項,解決了這個沖突的問題,但是由于增加了一個配置項,從客觀上增加了維護人員的工作量,也容易造成當維護人員不了解這里面的道道,忘記設置從而導致在一臺服務器中部署多個Tiny應用時多個應用中的模板文件生成的java文件和class文件的沖突,從而導致出現問題。

          PermSize內存占用問題

          采用編譯方式的時候,由于每個模板文件都要生成一個類,每個宏也要生成一個類,在宏調用過程中,也要生成一些類。(本來是可以不生成這些類的,但是由于Tiny模板引擎支持了一些非常有用的特性,所以宏調用時時采用編譯方式,就要生成一些內嵌類來完成)。這樣,就會生成大量的Java類,從工程非常大的時候,就會導致PermSize戰勝非常大。尤其是在系統還在調試的時候,模板文件變化的時候,就要重新編譯生成新的類,為了避免必須重新啟動應用服務器才能生生效,因此采用了自己編寫ClassLoader的方式來達到即時刷新的問題,但是由于Java的垃圾回收機制,決定了垃圾不是及時回收的,但是由于每個類都要有一個ClassLoader來支持,以便及時替換,因此這會進一步放大內存的占用。

          加載速度比較長的問題

          由于Tiny模板引擎中提供了宏,而這些宏可以獨立存在,因此在應用啟動的時候就必須加載所有的宏到內存中,以便查找。所以就導致第一次啟動的時候,由于要編譯所有的宏文件并加載之,導致啟動速度非常慢。在以后的啟動的時候,也必須檢測模板文件與生成的類是否一致,是否有被修改過,當a項目規模比較大的時候,這個時間也是比較長的。尤其是在開發期,啟動時間增加10秒,都會讓開發人員感覺到難以忍受。

          訪問速度的問題

          采用編譯方式的問題,在訪問上也有一些問題。

          為了提升應用啟動時間,只有宏文件是在啟動時預選編譯好并加載了的,而模板文件和布局文件則沒有這種待遇,這就導致如果在訪問的時候,第一次訪問的時候,需要編譯模板文件為java文件,再把java文件編譯為class文件,如果這次訪問還用到了布局文件,還import了其它的模板文件,那么悲劇了,第一個訪問者可能要多等待幾秒鐘的時間。同時,為了避免多次編譯情況的地生,還要增加同步鎖,這樣會進一步影響到訪問的效率。

          具體還沒有測試過ClassLoader太多對性能有多大的影響,但是毛估估是有一定影響的,畢竟要增加查找的層數。干的活多了,干的活慢了也是自然的,人是這樣,計算機也是同樣的道理。

          采用解釋方式帶來的好處

          由于采用解釋方式,因此不必生成java源文件和class文件,因此也就不存在文件路徑沖突的問題;同樣也不存在PermSize和眾多ClassLoader大量占用內存的問題。

          由于采用解釋方式,第一次加載,只定性掃描部分關系的內容即可,因此掃描速度非常快;只有在直接執行的時候,才需要更詳細的處理,同時由于不需要進行編譯,不需要做同步處理,因此加載速度會比編譯方式高許多,尤其是和編譯方式的第一次加載時間相比。

          訪問速度方面的問題,我原來的感覺來說,感覺編譯方式會快一些,畢竟它不用再云遍歷語法樹,但是實際執行下來,感覺解釋方式大致有一倍左右的提升,我分析了一下原因,大致可以認為是如下原因:1.由于Java的優化策略,導致使用頻率高的訪問會進行深度性能優化,采用解釋方式,由于用到的就是那幾個函數,因此可以很快滿足Java虛擬機的要求,更早的進行深度優化;2.由于解釋方式和編譯方式相比,可以采用更優化的解決方案,因此遍歷語法樹的時間由避免做一些事情彌補回來了,因此感受性能反而更高一點點。總之,這次編譯改解釋,的效果還是明顯的,各方面全面讓我滿意,尤其是前面擔心的執行效率方面也有大概50%左右的提升是讓我喜出望外的。還有一個意外之喜是通過把編譯方式改成解釋執行方式,代碼規模縮小了近一半,由原來的8000+行,變成4000+行。同時,由于不必要依賴JDT,antlr也只要依賴runtime包即可,還順便減少了3M的WAR包大小。

          OK,說了這么多,那就說說這次改造過程。

          由于團隊去島國旅游,當時把這個任務交給一個留守同學來完成,但是前后兩周的時候,沒有提交出我滿意的結果,由于看不到后續完成的時間節點,沒有辦法,只好我老先生親自動手來完成了,OK開工,相信仔細閱讀下面一節內容的同學,會對ANTLR解釋引擎的開發有深入了解,甚至拿我的代碼照葫蘆畫瓢,直接就可用。

          解釋引擎改造實錄

          解釋引擎總控類

          解釋引擎總控類是解釋引擎的核心,由于這個東東是為了Tiny模板引擎定制編寫的,因此如果有同學要拿來改造,請照葫蘆畫瓢即可。由于類不大,我就直接貼源碼上來,以便親們理解和我下面講解。

           

          1. public class TemplateInterpreter {  
          2.   
          3.     TerminalNodeProcessor[] terminalNodeProcessors = new TerminalNodeProcessor[200];  
          4.   
          5.     Map<Class<ParserRuleContext>, ContextProcessor> contextProcessorMap = new HashMap<Class<ParserRuleContext>, ContextProcessor>();  
          6.   
          7.     OtherTerminalNodeProcessor otherNodeProcessor = new OtherTerminalNodeProcessor();  
          8.   
          9.    
          10.   
          11.    
          12.   
          13.     public void addTerminalNodeProcessor(TerminalNodeProcessor processor) {  
          14.   
          15.         terminalNodeProcessors[processor.getType()] = processor;  
          16.   
          17.     }  
          18.   
          19.    
          20.   
          21.     public void addContextProcessor(ContextProcessor contextProcessor) {  
          22.   
          23.         contextProcessorMap.put(contextProcessor.getType(), contextProcessor);  
          24.   
          25.     }  
          26.   
          27.    
          28.   
          29.     public TinyTemplateParser.TemplateContext parserTemplateTree(String sourceName, String templateString) {  
          30.   
          31.         char[] source = templateString.toCharArray();  
          32.   
          33.         ANTLRInputStream is = new ANTLRInputStream(source, source.length);  
          34.   
          35.         // set source file name, it will be displayed in error report.  
          36.   
          37.         is.name = sourceName;  
          38.   
          39.         TinyTemplateParser parser = new TinyTemplateParser(new CommonTokenStream(new TinyTemplateLexer(is)));  
          40.   
          41.         return parser.template();  
          42.   
          43.     }  
          44.   
          45.    
          46.   
          47.     public void interpret(TemplateEngineDefault engine, TemplateFromContext templateFromContext, String templateString, String sourceName, TemplateContext pageContext, TemplateContext context, Writer writer) throws Exception {  
          48.   
          49.         interpret(engine, templateFromContext, parserTemplateTree(sourceName, templateString), pageContext, context, writer);  
          50.   
          51.         writer.flush();  
          52.   
          53.     }  
          54.   
          55.    
          56.   
          57.     public void interpret(TemplateEngineDefault engine, TemplateFromContext templateFromContext, TinyTemplateParser.TemplateContext templateParseTree, TemplateContext pageContext, TemplateContext context, Writer writer) throws Exception {  
          58.   
          59.         for (int i = 0; i < templateParseTree.getChildCount(); i++) {  
          60.   
          61.             interpretTree(engine, templateFromContext, templateParseTree.getChild(i), pageContext, context, writer);  
          62.   
          63.         }  
          64.   
          65.     }  
          66.   
          67.    
          68.   
          69.     public Object interpretTree(TemplateEngineDefault engine, TemplateFromContext templateFromContext, ParseTree tree, TemplateContext pageContext, TemplateContext context, Writer writer) throws Exception {  
          70.   
          71.         Object returnValue = null;  
          72.   
          73.         if (tree instanceof TerminalNode) {  
          74.   
          75.             TerminalNode terminalNode = (TerminalNode) tree;  
          76.   
          77.             TerminalNodeProcessor processor = terminalNodeProcessors[terminalNode.getSymbol().getType()];  
          78.   
          79.             if (processor != null) {  
          80.   
          81.                 returnValue = processor.process(terminalNode, context, writer);  
          82.   
          83.             } else {  
          84.   
          85.                 returnValue = otherNodeProcessor.process(terminalNode, context, writer);  
          86.   
          87.             }  
          88.   
          89.         } else if (tree instanceof ParserRuleContext) {  
          90.   
          91.             ContextProcessor processor = contextProcessorMap.get(tree.getClass());  
          92.   
          93.             if (processor != null) {  
          94.   
          95.                 returnValue = processor.process(this, templateFromContext, (ParserRuleContext) tree, pageContext, context, engine, writer);  
          96.   
          97.             }  
          98.   
          99.             if (processor == null || processor != null && processor.processChildren()) {  
          100.   
          101.                 for (int i = 0; i < tree.getChildCount(); i++) {  
          102.   
          103.                     Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer);  
          104.   
          105.                     if (value != null) {  
          106.   
          107.                         returnValue = value;  
          108.   
          109.                     }  
          110.   
          111.                 }  
          112.   
          113.             }  
          114.   
          115.    
          116.   
          117.         } else {  
          118.   
          119.             for (int i = 0; i < tree.getChildCount(); i++) {  
          120.   
          121.                 Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer);  
          122.   
          123.                 if (returnValue == null && value != null) {  
          124.   
          125.                     returnValue = value;  
          126.   
          127.                 }  
          128.   
          129.             }  
          130.   
          131.         }  
          132.   
          133.         return returnValue;  
          134.   
          135.     }  
          136.   
          137.    
          138.   
          139.     public static void write(Writer writer, Object object) throws IOException {  
          140.   
          141.         if (object != null) {  
          142.   
          143.             writer.write(object.toString());  
          144.   
          145.             writer.flush();  
          146.   
          147.         }  
          148.   
          149.     }  
          150.   
          151. }  

           

           
          這個類,所以行數是80行,去掉15行的import和package,也就是65行而已,從類的職能來看,主要完成如下事宜: 
          1. 管理了TerminalNodeProcessor和ParserRuleContext
          2. parserTemplateTree:解析文本內容獲取語法樹
          3. interpret:解釋執行語法樹
          4. interpret:遍歷所有節點并解釋執行之
          5. interpretTree:如果是TerminalNode那么找到合適的TerminalNode執行器去執行,如果找不到,則由OtherTerminalNodeProcessor去處理--實際上就是返回字符串了;如果是ParserRuleContext節點,那么就由對應的執行器去執行,執行完了看看是不是要執行子節點,如果需要,那么就繼續執行子節點,否則就返回。如果這兩種都不是,那就遍歷所有子節點去解釋執行了。

          所以邏輯還是比較清晰,最復雜的核心算法也只有30行,不管是什么樣層級的同學,看這些代碼都沒有任何難度了。

          需要交待的一件事情是:為什么ContextProcessor的處理類是用Map保存的,而TerminalNodeProcessor則是用數組?這里主要是為了考慮到TerminalNode都有一個類型,用數據的方式速度更快一些。

          上面說到有兩個接口,一個是處理TerminalNodeProcessor,另外一個是處理ContextProcessor的,下面交待一下這兩個接口。

          TerminalNodeProcessor

          1. public interface TerminalNodeProcessor<T extends ParseTree> {  
          2.   
          3.     int getType();  
          4.   
          5.     Object process(T parseTree, TemplateContext context, Writer writer) throws Exception;  
          6.   
          7. }  


           

          getType:用于返回處理器可處理的類型,用于解釋引擎檢查是不是你的菜
          1. process:真正的處理邏輯實現的地方

          ContextProcessor

          1. public interface ContextProcessor<T extends ParserRuleContext> {  
          2.   
          3.     Class<T> getType();  
          4.   
          5.    
          6.   
          7.     boolean processChildren();  
          8.   
          9.    
          10.   
          11.     Object process(TemplateInterpreter interpreter, TemplateFromContext templateFromContext, T parseTree, TemplateContext pageContext, TemplateContext context, TemplateEngineDefault engine, Writer writer) throws Exception;  
          12.   
          13.    
          14.   
          15. }  


          1. getType:用于返回處理器可處理的類型,用于解釋引擎檢查是不是你的菜
          2. processChildren:用于告訴引擎,你的兒子們是自己處理好了,還是讓解釋引擎繼續執行。返回true表示讓引擎繼續處理
          3. process:真正的處理邏輯實現的地方

          至此,整個解析引擎的框架就搭好了,剩下要做的就是去寫這些處理器了。

          TerminalNodeProcessor實現類示例

          其實這些實現類真的太簡單了,我都不好意思貼出來,為了讓大家看明白,貼幾個說說意思就好 

          DoubleNodeProcessor
          1. public class DoubleNodeProcessor implements TerminalNodeProcessor<TerminalNode> {  
          2.   
          3.     public int getType() {  
          4.   
          5.         return TinyTemplateParser.FLOATING_POINT;  
          6.   
          7.     }  
          8.   
          9.    
          10.   
          11.     public boolean processChildren() {  
          12.   
          13.         return false;  
          14.   
          15.     }  
          16.   
          17.    
          18.   
          19.     public Object process(TerminalNode terminalNode, TemplateContext context, Writer writer) {  
          20.   
          21.         String text=terminalNode.getText();  
          22.   
          23.         return Double.parseDouble(text);  
          24.   
          25.     }  
          26.   
          27. }  


          這貨的意思是:如果是Double類型的數據,就把字符串轉換成Double值返回。 

          StringDoubleNodeProcessor

          1. public class StringDoubleNodeProcessor implements TerminalNodeProcessor<TerminalNode> {  
          2.   
          3.     public int getType() {  
          4.   
          5.         return TinyTemplateParser.STRING_DOUBLE;  
          6.   
          7.     }  
          8.   
          9.     public boolean processChildren() {  
          10.   
          11.         return false;  
          12.   
          13.     }  
          14.   
          15.     public Object process(TerminalNode terminalNode, TemplateContext context, Writer writer) {  
          16.   
          17.         String text=terminalNode.getText();  
          18.   
          19.         text=text.replaceAll("\\\\\"","\"");  
          20.   
          21.         text=text.replaceAll("[\\\\][\\\\]","\\\\");  
          22.   
          23.         return text.substring(1, text.length() - 1);  
          24.   
          25.     }  
          26.   
          27. }  

          這貨的意思是,如果是雙引號引住的字符串,那么就把里面的一些轉義字符處理掉,然后把外面的雙引號也去掉后返回。 

          其它的和這個大同小異,總之非常簡單,想看的同學可以自己去看源碼,這里就不貼了。

          ContextProcessor類的實現示例

          這里面的處理,說實際的也沒有什么復雜的,主要原因是原來在寫模板引擎的時候,把運行時的一些東西,進行良好的抽象,因此這里只是個簡單的調用而已。這里貼2個稍微復雜的示范一下: 

          ForProcessor

          1. public class ForProcessor implements ContextProcessor<TinyTemplateParser.For_directiveContext> {  
          2.   
          3.    
          4.   
          5.     public Class<TinyTemplateParser.For_directiveContext> getType() {  
          6.   
          7.         return TinyTemplateParser.For_directiveContext.class;  
          8.   
          9.     }  
          10.   
          11.     public boolean processChildren() {  
          12.   
          13.         return false;  
          14.   
          15.     }  
          16.   
          17.     public Object process(TemplateInterpreter interpreter, TemplateFromContext templateFromContext, TinyTemplateParser.For_directiveContext parseTree, TemplateContext pageContext, TemplateContext context, TemplateEngineDefault engine, Writer writer) throws Exception {  
          18.   
          19.         String name = parseTree.for_expression().IDENTIFIER().getText();  
          20.   
          21.         Object values = interpreter.interpretTree(engine, templateFromContext, parseTree.for_expression().expression(),pageContext, context, writer);  
          22.   
          23.         ForIterator forIterator = new ForIterator(values);  
          24.   
          25.         context.put("$"+name + "For", forIterator);  
          26.   
          27.         boolean hasItem = false;  
          28.   
          29.         while (forIterator.hasNext()) {  
          30.   
          31.             TemplateContext forContext=new TemplateContextDefault();  
          32.   
          33.             forContext.setParent(context);  
          34.   
          35.             hasItem = true;  
          36.   
          37.             Object value = forIterator.next();  
          38.   
          39.             forContext.put(name, value);  
          40.   
          41.             try {  
          42.   
          43.                 interpreter.interpretTree(engine, templateFromContext, parseTree.block(),pageContext, forContext, writer);  
          44.   
          45.             } catch (ForBreakException be) {  
          46.   
          47.                 break;  
          48.   
          49.             } catch (ForContinueException ce) {  
          50.   
          51.                 continue;  
          52.   
          53.             }  
          54.   
          55.         }  
          56.   
          57.         if (!hasItem) {  
          58.   
          59.             TinyTemplateParser.Else_directiveContext elseDirectiveContext = parseTree.else_directive();  
          60.   
          61.             if (elseDirectiveContext != null) {  
          62.   
          63.                 interpreter.interpretTree(engine, templateFromContext, elseDirectiveContext.block(), pageContext,context, writer);  
          64.   
          65.             }  
          66.   
          67.         }  
          68.   
          69.         return null;  
          70.   
          71.     }  
          72.   
          73. }  

          這里解釋一下它的執行邏輯: 

          1. 首先獲取循環變量名
          2. 接下來獲取要循環的對象
          3. 然后構建一個循環迭代器,并在上下文中放一個循環變量進去
          4. 然后真正執行循環,如果有在循環過程中有break或continue指令,那么就執行之
          5. 如果最后一個循環也沒有執行,那么檢查 else 指令是否存在,如果存在就執行之

          是不是非常簡單?

          MapProcessor

          1. public class MapProcessor implements ContextProcessor<TinyTemplateParser.Expr_hash_mapContext> {  
          2.   
          3.     public Class<TinyTemplateParser.Expr_hash_mapContext> getType() {  
          4.   
          5.         return TinyTemplateParser.Expr_hash_mapContext.class;  
          6.   
          7.     }  
          8.   
          9.     public boolean processChildren() {  
          10.   
          11.         return false;  
          12.   
          13.     }  
          14.   
          15.     public Object process(TemplateInterpreter interpreter, TemplateFromContext templateFromContext, TinyTemplateParser.Expr_hash_mapContext parseTree, TemplateContext pageContext, TemplateContext context, TemplateEngineDefault engine, Writer writer) throws Exception {  
          16.   
          17.         List<TinyTemplateParser.ExpressionContext> expressions = parseTree.hash_map_entry_list().expression();  
          18.   
          19.         List<TinyTemplateParser.ExpressionContext> expressionContexts = expressions;  
          20.   
          21.         Map<String, Object> map = new HashMap<String, Object>();  
          22.   
          23.         if (expressions != null) {  
          24.   
          25.             for (int i = 0; i < expressions.size(); i += 2) {  
          26.   
          27.                 String key = interpreter.interpretTree(engine, templateFromContext, expressions.get(i), pageContext,context, writer).toString();  
          28.   
          29.                 Object value = interpreter.interpretTree(engine, templateFromContext, expressions.get(i + 1),pageContext, context, writer);  
          30.   
          31.                 map.put(key, value);  
          32.   
          33.             }  
          34.   
          35.         }  
          36.   
          37.         return map;  
          38.   
          39.     }  
          40.   
          41. }  


           

           

          這個是個構建MAP的處理器,它的執行邏輯是: 

          1. 新建個MAP對象,然后循環往MAP里put數據即可以了。
          2. 最后返回map對象

          我已經拿了最復雜的兩個來講了,其它的就更簡單了,因此就不再貼了,關心的同學們可以去看源代碼。

          總結

          1. 實際上用Java寫個新的語言啥的,沒有什么難的,難的是你心頭的那種恐懼,畢竟現在的一些開源框架如Antlr等的支持下,做詞法分析,語法樹構建是非常容易的一件事情,只要規劃并定義好語法規則,后面的實現并沒有多復雜。
          2. 好的設計會讓你受益頗多,Tiny模板引擎由編譯換成解釋執行,沒有什么傷筋動骨的變化,只是用新的方式實現了原有接口而已
          3. 對問題的分析的深入程度決定了你代碼編寫的復雜程度,上次和一個人討論時有說過:之所以你寫不簡單,是因為你考慮得還不夠多,分析的還不夠細
          4. 至此此次重構完成,正在測試當中,將在近日推出。  


          歡迎訪問開源技術社區:http://bbs.tinygroup.org。本例涉及的代碼和框架資料,將會在社區分享。《自己動手寫框架》成員QQ群:228977971,一起動手,了解開源框架的奧秘!或點擊加入QQ群:http://jq.qq.com/?_wv=1027&k=d0myfX

          posted on 2015-07-26 21:54 柏然 閱讀(101) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          <2015年7月>
          2829301234
          567891011
          12131415161718
          19202122232425
          2627282930311
          2345678

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 隆德县| 九龙坡区| 晋中市| 阿拉善左旗| 建湖县| 顺义区| 闽侯县| 卢氏县| 香格里拉县| 商都县| 高阳县| 同江市| 东光县| 若羌县| 汉沽区| 保山市| 枞阳县| 凤城市| 固原市| 古丈县| 东至县| 凉城县| 凌海市| 宝兴县| 云和县| 石河子市| 玛曲县| 松溪县| 弥勒县| 佛教| 泸州市| 阿图什市| 靖安县| 德格县| 治多县| 牙克石市| 绥化市| 瑞金市| 开化县| 珲春市| 神农架林区|