锁定老帖子 主题:FEL(表达式语言)——7月30号更新
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-03-11
变量名称和调用方式都会在运行时改变,公式也会被动态编辑,利用编译优化会比较难
利用profile这种思路有可行性,不用固化成文件,采取变量名按输入顺序与数组下标绑定,语法数的公式因子也与数组下标绑定,这样hash可以转化成顺序访问和间接寻址,但这种实现灵活性会下降,如果真这样写看代码的人肯定会骂人 通过预处理单独提速最后出结果这个环节只是测试好看些,需要提高的是取数加计算这个整体 |
|
返回顶楼 | |
发表时间:2011-03-11
最后修改:2011-03-11
ppgunjack 写道 变量名称和调用方式都会在运行时改变,公式也会被动态编辑,利用编译优化会比较难
利用profile这种思路有可行性,不用固化成文件,采取变量名按输入顺序与数组下标绑定,语法数的公式因子也与数组下标绑定,这样hash可以转化成顺序访问和间接寻址,但这种实现灵活性会下降,如果真这样写看代码的人肯定会骂人 通过预处理单独提速最后出结果这个环节只是测试好看些,需要提高的是取数加计算这个整体 对于变量名称的改变,处理方式是尽可能让变量名称不变,使用不同表达式执行上下文解析同一个变量,这样可以做到同一个变量返回不同值。如果考虑到高效性,也可以使用自定义的变量节点解释器来处理节点的值。在自定义解释器,可以使用数组、List等,以达到可以快速解析变量的目的。 对于公式会被动态编辑,处理方式是尽可以找出公式的变化和不变的地方。来进行优化。 如果你有具体的需求,我们可以详细探讨。 |
|
返回顶楼 | |
发表时间:2011-03-11
以前做的具体需求:在运行时用户可以编辑公式,嵌套定义公式,从数据库3000列中任意取其中某些列的数据计算成kpi,并且在时间上对这些kpi进行统计运算,其中有1,2元和多元运算
能想到的实现如果没有指针最快的方式是数组的方式间接挂靠,但可读性很差 |
|
返回顶楼 | |
发表时间: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 |
|
返回顶楼 | |
发表时间:2011-03-11
单纯计算表达式的时间意义不够,要考虑数据拿到后喂给表达式的时间
|
|
返回顶楼 | |
发表时间:2011-03-11
Interpreter Pattern而已。
|
|
返回顶楼 | |
发表时间:2011-03-11
ppgunjack 写道 单纯计算表达式的时间意义不够,要考虑数据拿到后喂给表达式的时间
我明白你的意思,也不是说我写的就,符合要求什么的,毕竟是练习手写dfa的时候写的。 我的主旨是在说,任何表达式都可以通过写字节码动态编译成java类,性能能提高很多。把会改变的部分和不会改变的部分剥离开,将动态的东西作为参数,静态的东西编译成java类,要比自己写的(反射的)解释器无论性能和内存消耗都要小很多。 另外,其实类似的东西可以使用jdk1.6自带的若干脚本引擎实现,它们也可以编译,并且支持的语法要多很多(不过既然你说是在造轮子这句话就当没说,呵呵) 再另外,楼上,---interpreter pattern---也是需要人写的,对不? 回头看看你的源码,也许可以commit一套编译工具~ |
|
返回顶楼 | |
发表时间:2011-03-11
动态性和速度之间真的很难取舍啊
|
|
返回顶楼 | |
发表时间:2011-03-11
最后修改:2011-03-11
其实,像这种需要用表达式的地方往往不是时常变动的(否则就用动态与言了)。
一般就是一个公式类的东西需要反复执行,这个公式可以带若干个参数或者一些调用。如果这种情况需要反复执行(如对数据库表中数据逐条执行)这种情况,那么编译出来一定效率更高。 编译时就用执行语句的顺序去写字节码,实际执行的时候就像原生代码一样高效了。 我这里使用的是objectweb的asm包,貌似cglib也行。不过好像由于它经过封装了貌似没那么灵活 |
|
返回顶楼 | |
发表时间:2011-03-11
ppgunjack 写道 以前做的具体需求:在运行时用户可以编辑公式,嵌套定义公式,从数据库3000列中任意取其中某些列的数据计算成kpi,并且在时间上对这些kpi进行统计运算,其中有1,2元和多元运算
能想到的实现如果没有指针最快的方式是数组的方式间接挂靠,但可读性很差 不知道你的需求是不是这样: 有一个公式与数据库中几个字段进行计算,这种需求我常遇到,其实使用Fel实现起来很简单,也高效。 |
|
返回顶楼 | |