论坛首页 Java企业应用论坛

FEL(表达式语言)——7月30号更新

浏览 13239 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-03-11  
变量名称和调用方式都会在运行时改变,公式也会被动态编辑,利用编译优化会比较难
利用profile这种思路有可行性,不用固化成文件,采取变量名按输入顺序与数组下标绑定,语法数的公式因子也与数组下标绑定,这样hash可以转化成顺序访问和间接寻址,但这种实现灵活性会下降,如果真这样写看代码的人肯定会骂人
通过预处理单独提速最后出结果这个环节只是测试好看些,需要提高的是取数加计算这个整体
0 请登录后投票
   发表时间:2011-03-11   最后修改:2011-03-11
ppgunjack 写道
变量名称和调用方式都会在运行时改变,公式也会被动态编辑,利用编译优化会比较难
利用profile这种思路有可行性,不用固化成文件,采取变量名按输入顺序与数组下标绑定,语法数的公式因子也与数组下标绑定,这样hash可以转化成顺序访问和间接寻址,但这种实现灵活性会下降,如果真这样写看代码的人肯定会骂人
通过预处理单独提速最后出结果这个环节只是测试好看些,需要提高的是取数加计算这个整体

对于变量名称的改变,处理方式是尽可能让变量名称不变,使用不同表达式执行上下文解析同一个变量,这样可以做到同一个变量返回不同值。如果考虑到高效性,也可以使用自定义的变量节点解释器来处理节点的值。在自定义解释器,可以使用数组、List等,以达到可以快速解析变量的目的。
对于公式会被动态编辑,处理方式是尽可以找出公式的变化和不变的地方。来进行优化。

如果你有具体的需求,我们可以详细探讨。
0 请登录后投票
   发表时间:2011-03-11  
以前做的具体需求:在运行时用户可以编辑公式,嵌套定义公式,从数据库3000列中任意取其中某些列的数据计算成kpi,并且在时间上对这些kpi进行统计运算,其中有1,2元和多元运算
能想到的实现如果没有指针最快的方式是数组的方式间接挂靠,但可读性很差
0 请登录后投票
   发表时间:2011-03-11   最后修改:2011-03-11
其实,针对固定的表达式是可以动态编译的,个人认为比较常用的表达式可以进行动态编译。
比如曾经见过的一些使用可配置(相对固定)公式计算的问题,使用动态编译的东西还是比较有用的。

我写过类似的东西,不过仅限四则表达式,呵呵,当时练习作品。使用纯手工dfa词法分析 +手工递归下降语法分析构造AST,手工写字节码动态编译表达式。。。不知道是不是有点启示。下面贴一下核心api以及简单性能测试(老爷机+开N多东西,1亿次)。有点多,如果有人感兴趣我就贴出来,呵呵
		//构造表达式对象
		CompilableExpression expression = ExpressionParser.parseExpression("a+(b+c)*d+e+f*g");
		//准备参数map
		Map<String, Double> map = new HashMap<String, Double>();
		map.put("a", 1500d);
		map.put("b", 40d);
		map.put("c", 59d);
		map.put("d", 88d);
		map.put("e", 1589d);
		map.put("f", 1155d);
		map.put("g", 13155d);
		//准备参数数组
		double[] param = expression.getParamArrayByMap(map);
		//执行1亿次
		int times = 100000000;
		
		long start, end;
		start = System.currentTimeMillis();
		for (int i = 0; i < times; i++) {
			expression.eval(map);//使用map作为参数
		}
		end = System.currentTimeMillis();
		System.out.println("不编译使用map:"+ (end - start));
		
		start = System.currentTimeMillis();
		for (int i = 0; i < times; i++) {
			expression.eval(param);//使用数组作为参数
		}

		end = System.currentTimeMillis();
		System.out.println("不编译使用数组:"+ (end - start));
		
		expression.compile();//编译
		
		start = System.currentTimeMillis();
		for (int i = 0; i < times; i++) {
			expression.eval(map);//编译后,使用map执行
		}
		end = System.currentTimeMillis();
		System.out.println("编译使用map:"+ (end - start));
		
		start = System.currentTimeMillis();
		for (int i = 0; i < times; i++) {
			expression.eval(param);//编译后使用数组执行
		}
		end = System.currentTimeMillis();
		System.out.println("编译使用数组:"+ (end - start));

		start = System.currentTimeMillis();
		TestPerformence t = new TestPerformence();
		for (int i = 0; i < times; i++) {
			t.getValue(param);//常规函数调用,params[0] + (params[1] + params[2]) * params[3] + params[4] + params[5] * params[6]
		}
		end = System.currentTimeMillis();
		System.out.println("调用函数访问数组执行:"+ (end - start));



结果:
不编译使用map:142304
不编译使用数组:55069
编译使用map:92915
编译使用数组:1717
调用函数访问数组执行:687

0 请登录后投票
   发表时间:2011-03-11  
单纯计算表达式的时间意义不够,要考虑数据拿到后喂给表达式的时间
0 请登录后投票
   发表时间:2011-03-11  
Interpreter Pattern而已。
0 请登录后投票
   发表时间:2011-03-11  
ppgunjack 写道
单纯计算表达式的时间意义不够,要考虑数据拿到后喂给表达式的时间

我明白你的意思,也不是说我写的就,符合要求什么的,毕竟是练习手写dfa的时候写的。
我的主旨是在说,任何表达式都可以通过写字节码动态编译成java类,性能能提高很多。把会改变的部分和不会改变的部分剥离开,将动态的东西作为参数,静态的东西编译成java类,要比自己写的(反射的)解释器无论性能和内存消耗都要小很多。

另外,其实类似的东西可以使用jdk1.6自带的若干脚本引擎实现,它们也可以编译,并且支持的语法要多很多(不过既然你说是在造轮子这句话就当没说,呵呵)

再另外,楼上,---interpreter pattern---也是需要人写的,对不?

回头看看你的源码,也许可以commit一套编译工具~
0 请登录后投票
   发表时间:2011-03-11  
动态性和速度之间真的很难取舍啊
0 请登录后投票
   发表时间:2011-03-11   最后修改:2011-03-11
其实,像这种需要用表达式的地方往往不是时常变动的(否则就用动态与言了)。
一般就是一个公式类的东西需要反复执行,这个公式可以带若干个参数或者一些调用。如果这种情况需要反复执行(如对数据库表中数据逐条执行)这种情况,那么编译出来一定效率更高。

编译时就用执行语句的顺序去写字节码,实际执行的时候就像原生代码一样高效了。

我这里使用的是objectweb的asm包,貌似cglib也行。不过好像由于它经过封装了貌似没那么灵活
0 请登录后投票
   发表时间:2011-03-11  
ppgunjack 写道
以前做的具体需求:在运行时用户可以编辑公式,嵌套定义公式,从数据库3000列中任意取其中某些列的数据计算成kpi,并且在时间上对这些kpi进行统计运算,其中有1,2元和多元运算
能想到的实现如果没有指针最快的方式是数组的方式间接挂靠,但可读性很差

不知道你的需求是不是这样:
有一个公式与数据库中几个字段进行计算,这种需求我常遇到,其实使用Fel实现起来很简单,也高效。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics