论坛首页 Java企业应用论坛

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

浏览 13193 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-03-16   最后修改:2011-03-16
ppgunjack 写道
以前demo的代码翻出来测试了一下
	static public void test3(){
	......	
        }

测试结果

表达式:kpi1/(kpi2*(kpi4+kpi5*kpi3)-kpi6)/kpi7
计算次数:100000000,花费时间:16992.0毫秒 16992
平均计算花费时间:1.6992E-4毫秒
9.832817520114257E-6
表达式:40.52334+60*(21.8144+17*32.663)
计算次数:100000000,花费时间:5477
34665.647339999996

cpu为intel8400,无其他负载
数字表达式如果单测大概2100左右,但放变量表达式后面一起跑就慢了一倍多,找不到原因
数组挂靠应该比数字表达式慢,但应该是一个数量级
以前做查询过滤器一次数据不会过20w,计算kpi每秒不会过w,对server速度足够了
写这个代码的时候cpu应该是amd3000+,测起来应该比较寒碜了,一直想换c++然后去函数化看看最快能到多少


如果你只是简单的算术运算的话,可以使用superobin兄的将表达式编译成字节码的方法,在我的老笔记机上执行表达式
"40.52334+60*(21.8144+17*32.663)"一亿次只需要不到一秒的时间。

放变量表达式后面一起跑就慢了一倍多,找不到原因,可以使用性能分析工具找找原因。
0 请登录后投票
   发表时间:2011-03-16  
sdh5724 写道
el 语言是个比较重要的东西, 有些系统需要一些动态执行脚本, 提取数据的能力。

楼上的, c++实现也未必快到哪儿去。 这点时间, 大部分系统下, 意义不是太。

在函数调用上可以实现内联去掉出入栈的开销
这个代码实现会调用每个计算节点的getExpressionValue()方法,在这个方法会根据运算符做实际运算,相比把运算变成字节码除了函数调用和返回,成本就是判断跳转上,应该是线性成本,属于一个数量级
函数开销对于这种计算占比比其他应用大
这个实现是针对逻辑和数值计算的混合计算,相比纯数值表达式,多了对每个下级节点类型的判断
不过就算我重写也达不到笔记本上1秒内1亿的速度
0 请登录后投票
   发表时间:2011-03-16  
使用c或者c++最大的优势可以很容易规避map的get,put导致的变量名的字符hash,对于绑定变量的加载和刷新减少的时间可以提高一个数量级,变量表达式和数字表达式的时间差距主要都是在从map的get上,
现实中的计算会是这样的情况,每一轮所有变量都要重新put,而这个喂数据部分的处理代码实际这个帖子的测试都没有包进去,所以只是时间上好看些而已
0 请登录后投票
   发表时间:2011-03-16  
lotusyu 写道
jjxlcsw 写道
ok, 等你的大作。。。。。。。。。

大作不敢当,交流一下还是可以的。留下你的邮箱,我发给你。

谢谢。我的邮箱是7810247101@sina.com.cn
0 请登录后投票
   发表时间:2011-03-16  
针String key ->double value的映射至数组的情况,我这用asm写了一个简单的解决方案,不知道对之前的讨论是否有用。
接口如下:
package calc.util.mapper;
public interface Mapper {
	public void put(String key,double value) ;
	public double[] getData();
}


调用如下:

package p;

import java.util.HashMap;
import java.util.Map;

import calc.util.mapper.Mapper;
import calc.util.mapper.MapperCreator;


public class TestBeanMap {
	public static void main(String[] args) throws Exception {
		Class c = MapperCreator.createMapperBean(new String[]{"a","b","c","d"});
		int times = 10000000;
		Mapper mapper = (Mapper)c.newInstance();
		long start = System.currentTimeMillis();
		for(int i=0;i<times;i++) {
			mapper.put("a", 1123);
			mapper.put("b", 1123);
			mapper.put("c", 1123);
			mapper.put("d", 1123);
			mapper.getData();
		}
		long end = System.currentTimeMillis();
		System.out.print("用动态编译mapper:");
		System.out.println(end-start);
		start = System.currentTimeMillis();
		Map m = new HashMap();
		for(int i=0;i<times;i++) {
			m.put("a",1123);
			m.put("b",1123);
			m.put("c",1123);
			m.put("d",1123);
			m.get("a");
			m.get("b");
			m.get("c");
			m.get("d");
		}
		end = System.currentTimeMillis();
		System.out.print("使用hashmap:");
		System.out.println(end-start);
		Mapper newMapper = new MapperInstance();
		start = System.currentTimeMillis();
		for(int i=0;i<times;i++) {
			newMapper.put("a", 1123);
			newMapper.put("b", 1123);
			newMapper.put("c", 1123);
			newMapper.put("d", 1123);
			newMapper.getData();
		}
		end = System.currentTimeMillis();
		System.out.print("使用静态mapper:");
		System.out.println(end-start);
		
	}
	
}
class MapperInstance implements Mapper{
	double[] data;
	public MapperInstance() {
		data = new double[4];
	}
	public double[] getData() {
		return data;
	}

	public void put(String key, double value) {
		if("a".equals(key)) {
			data[0] = value;
		} else if("b".equals(key)) {
			data[1] = value;
		}else if("c".equals(key)) {
			data[2] = value;
		}else if("d".equals(key)) {
			data[3] = value;
		}
		
	}
	
}

结果是:

用动态编译mapper:1349
使用hashmap:2741
使用静态mapper:1391


动态编译代码:
package calc.util.mapper;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public abstract class MapperCreator implements Opcodes{
	protected double[] data;
	public  static Class<? extends Mapper> createMapperBean(String[] propertyNames) throws Exception{
		return createMapperBean(propertyNames, "fl.calc.MapperBean");
	}
	private static Class<? extends Mapper> createMapperBean(String[] propertyNames,final String className ) throws Exception{
		// 類頭
		String[] itfs = {Mapper.class.getName().replace(".", "/")};//注意:接口名中的.要替換成/
		ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
		String slashedClassName = className.replace(".", "/");
		cw.visit(V1_5, ACC_PUBLIC, slashedClassName,null, "java/lang/Object", itfs);
		cw.visitField(ACC_PUBLIC, "data", "[D",  null, null).visitEnd();  
	
		// 默認構造方法
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
		mv.visitVarInsn(ALOAD, 0);//讀入this
		mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");//調用默認構造方法中的初始化
		mv.visitVarInsn(ALOAD, 0);//讀入this
		
		int constNum = getConstNum(propertyNames.length);
		if(constNum>=0) {
			mv.visitInsn(constNum);
		} else {
			mv.visitIntInsn(SIPUSH, propertyNames.length);
		}
		mv.visitIntInsn(NEWARRAY,T_DOUBLE);//创建数组
		mv.visitFieldInsn(PUTFIELD, slashedClassName, "data","[D");//为data变量赋值
		mv.visitInsn(RETURN);//函數返回
		mv.visitMaxs(1, 1);
		mv.visitEnd();
		
		mv = cw.visitMethod(ACC_PUBLIC,"getData","()[D",null,null);//getdata方法
		mv.visitVarInsn(ALOAD, 0);//装入this
		mv.visitFieldInsn(GETFIELD, slashedClassName, "data", "[D");//获取到数组
		mv.visitInsn(ARETURN);//返回数组
		mv.visitMaxs(1, 1);
		mv.visitEnd();
		
		mv = cw.visitMethod(ACC_PUBLIC, "put", "(Ljava/lang/String;D)V", null,null);//put方法
		Label exit = new Label();//声明一个label,跳转到结束
		for (int i = 0; i < propertyNames.length; i++) {
			Label next = new Label();
			mv.visitLdcInsn(propertyNames[i]);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
			mv.visitJumpInsn(IFEQ, next);
			mv.visitVarInsn(ALOAD, 0);
			mv.visitFieldInsn(GETFIELD, slashedClassName, "data", "[D");
			constNum = getConstNum(i);
			if(constNum>=0) {
				mv.visitInsn(constNum);
			} else {
				mv.visitIntInsn(SIPUSH, i);
			}
			mv.visitVarInsn(DLOAD, 2);
			mv.visitInsn(DASTORE);
			mv.visitJumpInsn(GOTO	, exit);
			mv.visitLabel(next);
		}
		mv.visitLabel(exit);
		mv.visitInsn(RETURN);
		mv.visitMaxs(1, 1);
		mv.visitEnd();
		cw.visitEnd();
		return applyClass(className, cw.toByteArray());
	}
	/**
	 * 优化向堆栈压入Int数据,如果在5以内自动使用常数
	 * @param i
	 * @return
	 */
	private static int getConstNum(int i) {
		int constNum;
		if(i== 0) {
			constNum = ICONST_0;
		} else if(i==1) {
			constNum = ICONST_1;
		} else if(i==2) {
			constNum = ICONST_2;
		} else if(i==3) {
			constNum = ICONST_3;
		} else if(i==4) {
			constNum = ICONST_4;
		} else if(i==5) {
			constNum = ICONST_5;
		} else {
			constNum = -1;
		}
		return constNum;
	}
	
	@SuppressWarnings("unchecked")
	private static Class<? extends Mapper> applyClass(final String className, byte[] bytes) {
		return new ClassLoader() {
			public Class defineClass(byte[]bytes) {
				return defineClass(className,bytes, 0, bytes.length);
			}
		}.defineClass(bytes);
	}
}

应该和原生代码基本一样了。有一点点可以优化的地方貌似不过应该完全不影响性能。
0 请登录后投票
   发表时间:2011-03-17  
package calc.util.mapper;
    Map m = new HashMap();  
        m.put("kpi1",40);  
        m.put("kpi2",34);  
        m.put("kpi3",34);  
        m.put("kpi4",44);
        m.put("kpi5",44);  
        m.put("kpi6",344);  
        m.put("kpi7",344); 
        for(int i=0;i<times;i++) {  
            m.get("kpi1");  
            m.get("kpi2");  
            m.get("kpi3");  
            m.get("kpi4"); 
            m.get("kpi5");  
            m.get("kpi6");  
            m.get("kpi7");  
        }  

你代码中计算是按1千万计算的
上面这段代码单测了下hashmap单纯读1亿次的成本,单位笔记本跑的无太多负载,笔记本p8400 2.26g,比家里慢很多

使用hashmap:14399

如果把put放入循环,则时间上升到

使用hashmap:45489

可以看到基于名字匹配方式的变量的get和put成本远远超过计算本身了
其实即使是if{}elseif{}和字符串的equal这些运算本身已经和节点运算当量相当了,当然在变量数量少的情况,相比hash有优势,但这种方式成本都和变量长度正相关
0 请登录后投票
   发表时间:2011-03-17  
现在表达式本身已经成本很低,主要集中在节点数值逻辑输出判断,节点下级子节点类型判断,变量get,函数调用,符号计算,除了函数调用和变量get能榨出油水的地方不多
0 请登录后投票
   发表时间:2011-03-17   最后修改:2011-03-17
ppgunjack 写道
变量名称和调用方式都会在运行时改变,公式也会被动态编辑,利用编译优化会比较难
利用profile这种思路有可行性,不用固化成文件,采取变量名按输入顺序与数组下标绑定,语法数的公式因子也与数组下标绑定,这样hash可以转化成顺序访问和间接寻址,但这种实现灵活性会下降,如果真这样写看代码的人肯定会骂人
通过预处理单独提速最后出结果这个环节只是测试好看些,需要提高的是取数加计算这个整体



我是用这种手段实现了map->数组的转变,getData()返回的是按顺序排好的数组,比hashMap中用get再取出来 速度要提高很多。
我这里说的不是节点运算的问题,关键是 要把 字符串定义的变量 收纳至数组中,在预编译的代码中使用数组要比map速度快很多。

光说性能,确实,当变量很多的时候这个Mapper的put性能不如map,但是这里取数据直接是数组所以整体性能应该还是能榨出油的。何况一般公式的参数个数还不足以令人发指。如果真的很多可以给一个阈值,多少之内使用动态编译多少之上使用hashmap,把细节隐藏在工厂方法中。
0 请登录后投票
   发表时间:2011-03-17  
你的思路很开阔,我其实想说的就是在计算表达式这种应用,喂数据的方式已经是和计算一样是需要考虑成本的,而且成本可能还更高,对于一些应用可以绕过,比如直接数据库列号映射计算表达式数组,规避循环利用名字的变量定位,这个实现我觉得性能应该和纯数字表达式的速度差不多
但是相对有指针的语言在规避这个消耗上面要容易很多不需要特别设计
0 请登录后投票
   发表时间:2011-03-17  
原来写这个的时候公司已经用一个叫jbell的库做了实现,但我感觉性能不好,所以自己用的时候重写了实现,做的时候就是尽量规避两个事情:继承,字符串处理
在做计算的时候,尽量都用switch和数字比较来处理逻辑,但对变量这个问题一直想不出合适方案,总会在某个地方引入循环内调用的字符串函数
0 请登录后投票
论坛首页 Java企业应用版

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