莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          高性能EL——Fel探秘,兼談EL

          Posted on 2011-09-17 12:52 dennis 閱讀(9953) 評論(5)  編輯  收藏 所屬分類: java源碼解讀計算機科學與基礎
              Fel是最近javaeye比較火的關鍵詞,這是由網友lotusyu開發的一個高性能的EL,從作者給出的數據來看,性能非常優異,跟前段時間溫少開源的Simple EL有的一拼。首先要說,這是個好現象,國內的開源項目越來越多,可以看出開發者的水平是越來越高了,比如我最近還看到有人開源的類似kestel的輕量級MQ——fqueue也非常不錯,有興趣可以看下我的分析《fqueue初步分析》。

              進入正文,本文是嘗試分析下Fel的實現原理,以及優缺點和aviator——我自己開源的EL之間的簡單比較。

              Fel的實現原理跟Simple EL是類似,都是使用template生成中間代碼——也就是普通的java代碼,然后利用javac編譯成class,最后運行,當然,這個過程都是動 態的。JDK6已經引入了編譯API,在此之前的版本可以調用sun的類來編譯,因為javac其實就是用java實現的。回到Fel里 面,FelCompiler15就是用 com.sun.tools.javac.Main來編譯,而FelCompiler16用標準的javax.tools.JavaCompiler來編譯的。

              文法和語法解釋這塊是使用antlr這個parse generator生成的,這塊不多說,有興趣可以看下antlr,整體一個運行的過程是這樣:

              expression string -> antlr -> AST -> comiple -> java source template -> java class -> Expression 

              這個思路我在實現aviator之前就想過,但是后來考慮到API需要用的sun獨有的類,而且要求classpath必須有tools.jar這個依賴包,就放棄了這個思路,還是采用ASM生成字節碼的方式。題外,velocity的優化可以采用這個思路,我們有這么一個項目是這么做的,也準備開源了。

           

              看看Fel生成的中間代碼,例如a+b這樣的一個簡單的表達式,假設我一開始不知道a和b的類型,編譯是這樣:

              FelEngine fel = new FelEngineImpl();  
              Expression exp 
          =  fel.compile("a+b"null); 

              我稍微改了下FEL的源碼,讓它打印中間生成的java代碼,a+b生成的中間結果為:

              package com.greenpineyu.fel.compile;  
                
              
          import com.greenpineyu.fel.common.NumberUtil;  
              
          import com.greenpineyu.fel.Expression;  
              
          import com.greenpineyu.fel.context.FelContext;  
              
          import org.apache.commons.lang.ObjectUtils;  
              
          import org.apache.commons.lang.StringUtils;  
                
              
          public class Fel_0  implements Expression{  
                
                  
          public Object eval(FelContext context) {  
                      java.lang.Object var_1 
          = (java.lang.Object)context.get("b");   //b  
                      java.lang.Object var_0 = (java.lang.Object)context.get("a");   //a  
                      return (ObjectUtils.toString(var_0))+(ObjectUtils.toString(var_1));  
                  }  
              } 

               可見,FEL對表達式解析和解釋后,利用template生成這么一個普通的java類,而a和b都從context中獲取并轉化為Object類型,這里沒有做任何判斷就直接認為a和b是要做字符串相加,然后拼接字符串并返回。

           

               問題出來了,因為沒有在編譯的時候傳入context(我們這里是null),FEL會將a和b的類型默認都為java.lang.Object,a+b解釋為字符串拼接。但是運行的時候,我完全可以傳入a和b都為數字,那么結果就非常詭異了:

               FelEngine fel = new FelEngineImpl();  
                
              Expression exp 
          = fel.compile("a+b"null);  
              Map
          <String, Object> env=new HashMap<String, Object>();  
              env.put(
          "a"1);  
              env.put(
          "b"3.14);  
              System.out.println(exp.eval(
          new MapContext(env))); 

          輸出:

              13.14 

              1+3.14的結果,作為字符串拼接就是13.14,而不是我們想要的4.14。如果將表達式換成a*b,就完全運行不了

              com.greenpineyu.fel.exception.CompileException: package com.greenpineyu.fel.compile;  
                
              
          import com.greenpineyu.fel.common.NumberUtil;  
              
          import com.greenpineyu.fel.Expression;  
              
          import com.greenpineyu.fel.context.FelContext;  
              
          import org.apache.commons.lang.ObjectUtils;  
              
          import org.apache.commons.lang.StringUtils;  
                
              
          public class Fel_0  implements Expression{  
                
                  
          public Object eval(FelContext context) {  
                      java.lang.Object var_1 
          = (java.lang.Object)context.get("b");   //b  
                      java.lang.Object var_0 = (java.lang.Object)context.get("a");   //a  
                      return (var_0)*(var_1);  
                  }  
              }  
                
              [Fel_0.java:
          14: 運算符 * 不能應用于 java.lang.Object,java.lang.Object]  
                  at com.greenpineyu.fel.compile.FelCompiler16.compileToClass(FelCompiler16.java:
          113)  
                  at com.greenpineyu.fel.compile.FelCompiler16.compile(FelCompiler16.java:
          87)  
                  at com.greenpineyu.fel.compile.CompileService.compile(CompileService.java:
          66)  
                  at com.greenpineyu.fel.FelEngineImpl.compile(FelEngineImpl.java:
          62)  
                  at TEst.main(TEst.java:
          14)  
              Exception in thread 
          "main" java.lang.NullPointerException  
                  at TEst.main(TEst.java:
          18

           

              這個問題對于Simple EL同樣存在,如果沒有在編譯的時候能確定變量類型,這無法生成正確的中間代碼,導致運行時出錯,并且有可能造成非常詭異的bug。

           

              這個問題的本質是因為Fel和Simple EL沒有自己的類型系統,他們都是直接使用java的類型的系統,并且必須在編譯的時候確定變量類型,才能生成高效和正確的代碼,我們可以將它們稱為“強類型的EL“。

           

              現在讓我們在編譯的時候給a和b加上類型,看看生成的中間代碼:

              FelEngine fel = new FelEngineImpl();  
              fel.getContext().set(
          "a"1);  
              fel.getContext().set(
          "b"3.14);  
              Expression exp 
          = fel.compile("a+b"null);  
              Map
          <String, Object> env = new HashMap<String, Object>();  
              env.put(
          "a"1);  
              env.put(
          "b"3.14);  
              System.out.println(exp.eval(
          new MapContext(env))); 

              查看中間代碼:

              package com.greenpineyu.fel.compile;  
                
              
          import com.greenpineyu.fel.common.NumberUtil;  
              
          import com.greenpineyu.fel.Expression;  
              
          import com.greenpineyu.fel.context.FelContext;  
              
          import org.apache.commons.lang.ObjectUtils;  
              
          import org.apache.commons.lang.StringUtils;  
                
              
          public class Fel_0  implements Expression{  
                
                  
          public Object eval(FelContext context) {  
                      
          double var_1 = ((java.lang.Number)context.get("b")).doubleValue();   //b  
                      double var_0 = ((java.lang.Number)context.get("a")).doubleValue();   //a  
                      return (var_0)+(var_1);  
                  }  
              } 

          可以看到這次將a和b都強制轉為double類型了,做數值相加,結果也正確了:

              4.140000000000001 

              Simple EL我沒看過代碼,這里猜測它的實現也應該是類似的,也應該有同樣的問題。

              相比來說,aviator這是一個弱類型的EL在編譯的時候不對變量類型做任何假設,而是在運行時做類型判斷和自動轉化。過去提過,我給aviator的定位是一個介于EL和script之間的東西,它有自己的類型系統。 例如,3這個數字,在java里可能是long,int,short,byte,而aviator統一為AviatorLong這個類型。為了在這兩個類 型之間做適配,就需要做很多的判斷和box,unbox操作。這些判斷和轉化都是運行時進行的,因此aviator沒有辦法做到Fel這樣的高效,但是已 經做到至少跟groovy這樣的弱類型腳本語言一個級別,也超過了JXEL這樣的純解釋EL,具體可以看這個性能測試

           

             強類型還是弱類型,這是一個選擇問題,如果你能在運行前就確定變量的類型,那么使用Fel應該可以達到或者接近于原生java執行的效率,但是失去了靈活性;如果你無法確定變量類型,則只能采用弱類型的EL。

           

             EL涌現的越來越多,這個現象有點類似消息中間件領域,越來越多面向特定領域的輕量級MQ的出現,而不是原來那種大而笨重的通用MQ大行其道,一方面是互 聯網應用的發展,需求不是通用系統能夠滿足的,另一方面我認為也是開發者素質的提高,大家都能造適合自己的輪子。從EL這方面來說,我也認為會有越來越多 特定于領域的,優點和缺點一樣鮮明的EL出現,它們包含設計者自己的目標和口味,選擇很多,就看取舍。


          評論

          # re: 高性能EL——Fel探秘,兼談EL  回復  更多評論   

          2011-09-17 14:38 by GhostZhang
          強人啊!

          # re: 高性能EL——Fel探秘,兼談EL  回復  更多評論   

          2011-09-18 08:39 by tb
          強人

          # re: 高性能EL——Fel探秘,兼談EL  回復  更多評論   

          2011-09-19 11:06 by kino.lucky
          前段時間,也看到Fel的介紹,對這個挺感興趣的,但沒研究過,謝謝你的文章。
          對這個用途我有一點想法,在現在做互聯網項目的時候,不管是前后端通信,還是做api的時候,用到json的地方越來越多,但后端生成json字符串的時候總感覺不夠好,一是用各種java data bean 組裝成一個JSONObject或Map,然后轉化成一個json字符串,但這種方式一點都不直觀,對后面維護代碼和變更都不方便。后來就想到用模板文件,比如用FreeMarker或Velocity做一個Json的映射文件,這樣是直觀了很多,一目了然,修改也很方便,但是感覺FreeMarker和Velocity又不適合,性能不好也太重量級。記得去年就看到你的aviator(當時記得說到命名的由來,映像挺深的),發現性能不錯,當時就想能不能用于json轉換。所以我在想能不能開發一個額外的工具包,專門用于復雜Json對象的高效生成。呵呵,不知有沒有人和我一樣的想法?

          # re: 高性能EL——Fel探秘,兼談EL  回復  更多評論   

          2011-09-19 21:30 by zkgale
          不知道能不能分析一下nutz的el實現呢?
          它主要思路是通過逆波蘭進行轉換,并生成一個執行鏈

          # re: 高性能EL——Fel探秘,兼談EL[未登錄]  回復  更多評論   

          2011-12-28 11:27 by James
          拜讀了大師的aviator源碼,發現詞法分析和語法分析是大師手工寫的嗯,非常強大.由于編譯原理學的不是很好,對于ExpressionParser里面的代碼看的不是很明白,感覺上是根據BNF轉換成的代碼.
          希望大師指點一二,提供一些參考資料.很想自己也寫個類似的東東提高一下.
          主站蜘蛛池模板: 唐山市| 天气| 健康| 大余县| 吴旗县| 韶山市| 科技| 云阳县| 嘉黎县| 盘锦市| 柳河县| 长葛市| 布拖县| 周至县| 上林县| 福泉市| 光山县| 成都市| 竹山县| 营口市| 榆社县| 平利县| 驻马店市| 铜陵市| 安平县| 贵南县| 神农架林区| 普宁市| 同江市| 观塘区| 长沙市| 清原| 泰宁县| 新竹县| 锡林郭勒盟| 栾川县| 峨眉山市| 天峻县| 务川| 蓝田县| 那曲县|