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

标签: 未分类 | 发表时间:2011-09-17 16:54 | 作者:boyan 冷月
出处:http://rdc.taobao.com/team/jm

    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.comp

 

    我稍微改了下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+bnull);  
    Map
<String, Object> env=new HashMap<String, Object>();  
    env.put(
a1);  
    env.put(
b3.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(
a1);  
    fel.getContext().set(
b3.14);  
    Expression exp 
= fel.compile(a+bnull);  
    Map
<String, Object> env = new HashMap<String, Object>();  
    env.put(
a1);  
    env.put(
b3.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出现,它们包含设计者自己的目标和口味,选择很多,就看取舍。

相关 [性能 el fel] 推荐:

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

- 冷月 - 淘宝JAVA中间件团队博客
    Fel是最近javaeye比较火的关键词,这是由网友lotusyu开发的一个高性能的EL,从作者给出的数据来看,性能非常优异,跟前段时间温少开源的Simple EL有的一拼. 首先要说,这是个好现象,国内的开源项目越来越多,可以看出开发者的水平是越来越高了,比如我最近还看到有人开源的类似kestel的轻量级MQ——fqueue也非常不错,有兴趣可以看下我的分析《fqueue初步分析》.

El-Hadji Diouf 恶搞 GIF(29P)

- 稀客魔 - Poboo
这是一张普通的足球比赛,球员是格拉斯哥流浪者队的球员El-Hadji Diouf,我是真不认识这哥们,google 查了一下,只说说他驾$600,000美金的Mercedes McLaren跑车,看来也是个有钱的银, 这不关键,关键是这哥们通过这场比赛,彻底红了,民间高手通过他大展身手,他的转身,成为了互联网上各种版本的传说.

Fel是轻量级的高效的表达式计算引擎

- - CSDN博客推荐文章
Fel是轻量级的高效的表达式计算引擎. Fel在源自于企业项目,设计目标是为了满足不断变化的功能需求和性能需求. Fel是开放的,引擎执行中的多个模块都可以扩展或替换. Fel的执行主要是通过函数实现,运算符(+、-等都是Fel函数),所有这些函数都是可以替换的,扩展函数也非常简单. Fel有双引擎,同时支持解释执行和编译执行.

Java EL 详细用法讲解

- - Java - 编程语言 - ITeye博客
本文主要介绍了Java EL的语法结构以及EL的使用方法,并结合例子代码讲解了Java EL对象、Java EL运算符以及Java EL函数. EL 提供.和[]两种运算符来存取数据. 当要存取的属性名称中包含一些特殊字符,如.或?等并非字母或数字的符号,就一定要使用 []. ${user.My-Name}应当改为${user["My-Name"] }.

NVIDIA 发布 Kal-El 白皮书,隐藏有第五个核心

- NOir - Engadget 中国版
我们已经知晓 Kal-El 的存在,我们期待着配置这个处理器的平板电脑和智能手机上市,不过关于这个处理器的优势在这之前还是个秘密,我们知道的并不多. 幸运的是,NVIDIA 这次发布了 Kal-El 白皮书,告诉我们这个拥有更高性能,更好电池续航力,更优游戏图像表现的处理器不仅仅是由四个主核心提供的,还有一个隐藏核心共同实现.

NVIDIA 發佈 Kal-El 白皮書,隱藏有第五個核心

- SotongDJ - Engadget 中文版
我們已經知曉 Kal-El 的存在,期待著配置這個處理器的平板電腦和智能手機上市,不過關於這個處理器的優勢在這之前還是個秘密,我們知道的並不多. 幸運的是,NVIDIA 這次發布了 Kal-El 白皮書,告訴我們這個擁有更高性能,更好電池續航力,更優秀遊戲圖像表現的處理器不僅僅是由四個主核心提供的,還有一個隱藏核心共同實現.

中兴Kal-El四核Android平板T98现身 支持TD-SCDMA

- xing - cnBeta.COM
2011年中国通信展于昨日在北京正式开幕,中兴通讯展台除了诸多采用Android、Windows Phone 7.5 "Mango"操作系统的新品手机外,一款平板也颇引人注意. 根据中兴提供的介绍,这款型号为T98的平板电脑采用的是NVIDIA开发代号为"Kal-El"的下一代四核Tegra处理器,而T98也抢在了华硕Eee Pad Transformer二代前使Kal-El/Android组合首次亮相.

NVIDIA Kal-El Windows 8 參考平台(又)動眼看

- SotongDJ - Engadget 中文版
雖然這未必是世界第一部運行 Windows 8 的 Kal-El 平板,但至少這會是第一部我們能夠接觸到,同時運行這顆矚目的 NVIDIA 4 核心處理器和 Windows 8 的裝置. 正如我們之前提到,NVIDIA 剛啟動了其 Windows 8 開發計劃,而這開發計劃的積極性也引起了我們的興趣.

华硕Transformer 2可能成为首款NVIDIA Kal-El核心设备

- 洞箫 - cnBeta.COM
华硕第二代Transformer平板产品虽然有可能被延迟,但这依然不影响它成为首款NVIDIA Kal-El核心的设备,来自Fudzilla的消息显示,华硕Transformer 2将是第一款装载1.5GHz四核心Tegra 3芯片的平板.

4コアTegra 3 " Kal-El " 搭載の7型タブレット、中国ZTEが公開

- 三十不归 - Engadget Japanese
NVIDIA " Kal-El " 採用タブレットが意外なところから登場しました. 写真は北京国際通信展にZTEが出展したタブレット T98. 1280 x 800画面で11.5mm厚、中国のTD-CDMAに対応する一般的な7インチ Honeycomb タブレットながら、製品概要にははっきり「NVIDIA Tegra 3」採用と記されています.