最近的一个项目需要进行蒙特卡洛模拟,并且需要对模拟结果进行表达式求职计算
计算的公式不固定一般的格式是这样$0+$1*2/$3*2.5且随着业务的复杂还需要
进行多次迭代计算。数据量很大最少的也要进行2-3万次计算。我试了用自己写的表达式求职程序和Mozilla的Rhino效率上都不是很理想,可能是自己
水平比较差,用Mozilla的Rhino居然比自己写的程序快一倍。我们用的jdk1.5所以不能
用jdk1.6中才支持的编译后执行脚本的方法。想过尝试动态生成Java文件然后编译执行,但是一想起在服务器环境这个程序可能需要
同时计算多个表达式......越想越觉得不可靠.大家处理这样的问题有什么好的思路,请给些指点.
计算的公式不固定一般的格式是这样$0+$1*2/$3*2.5且随着业务的复杂还需要
进行多次迭代计算。数据量很大最少的也要进行2-3万次计算。我试了用自己写的表达式求职程序和Mozilla的Rhino效率上都不是很理想,可能是自己
水平比较差,用Mozilla的Rhino居然比自己写的程序快一倍。我们用的jdk1.5所以不能
用jdk1.6中才支持的编译后执行脚本的方法。想过尝试动态生成Java文件然后编译执行,但是一想起在服务器环境这个程序可能需要
同时计算多个表达式......越想越觉得不可靠.大家处理这样的问题有什么好的思路,请给些指点.
你可以先把一个固定的计算公式用java写好,然后跟Rhino比较一下。保守的估计至少要差几十倍以上,rhino的脚本编译运行也不能提高太多的效率,因为它依旧要依靠脚本运行引擎。如果计算公式不是经常变化的话,还是可以用java写,用javac动态编译。同时可以考虑用文件保存编译过的class。
有可能会在前段做一个界面出来,用户可以拖拽连线来构造计算树,然后发给后台计算。
所以是不可能将计算过程固定的,解决办法只能有两种:
1.用脚本
2.将计算过程翻译成Java代码编译后执行,而这一过程的难点是编译后的文件怎么保存到哪?编译后的类如何加载。因为现实的情况很有可能是这一模块正同时处理多个不同的计算,必须要有一种策略让虚拟知道如何正确的
加载编译文件
函数会被多次调用/**
*计算表达式的值并将结果赋值给返回数组.
*比如eval("$0+$1*2+$3",result,d0,d1,d2,d3),最后result应
*该能够正确的得到计算值
*@param sExp 计算表达式,$0表示datas[0][i],$1表示datas[i][i]
*@param result 返回结果存放数据
*@param datas 计算相关数据
*/public void eval(String sExp,
double[] result,
double[]...datas)throws Exception{}
import bsh.Interpreter;public class Test2 {
public static void main(String[] args) throws EvalError {
final int count = 5000;
final int paramCount = 4;
double[][] datas = new double[paramCount][count];
Random ran = new Random();
for(int i = 0; i < datas.length; i++) {
for(int j = 0; j < datas[i].length; j++) {
datas[i][j] = ran.nextDouble() * count;
}
}
double[] results = new double[count];
long t0, t1;
t0 = System.currentTimeMillis();
String exp = "$0 + $1 * $2 + $3";
eval(exp, results, datas);
t1 = System.currentTimeMillis();
System.out.println(t1 - t0);
// 测试正确性
double[] res = new double[count];
for(int j = 0; j < datas[0].length; j++) {
for(int i = 0; i < datas.length; i++) {
res[j] = datas[0][j] + datas[1][j] * datas[2][j] + datas[3][j];
}
}
for(int i = 0; i < count; i++) {
if(results[i] != res[i]) {
System.out.printf("error! %.6f --> %.6f%n", results[i], res[i]);
}
}
}
public static void eval(String expression, double[] results, double[]... datas) throws EvalError {
String ep = expression.replaceAll("\\$(\\d+)", "datas[$1][j]");
String exp =
" for(int j = 0; j < datas[0].length; j++) {" +
" for(int i = 0; i < datas.length; i++) {" +
" results[j] = " + ep + ";" +
" } " +
" }";
Interpreter bsh = new Interpreter();
bsh.set("datas", datas);
bsh.set("results", results);
bsh.eval(exp);
results = (double[])bsh.get("results");
}
}BeanShell 的官方网站:http://www.beanshell.org/
可能是你使用方式不当造成的,不要计算一次调用一次,把计算的过程用脚本写成循环那样只需要调用一次。
我用Rhino是这样用的:
double[] d1=new double[5000];
double[] d2=new double[5000];
double[] ret=new double[5000];
.....
1.将java中的数据映射到脚本
ScriptableObject.putProperty(scope,"d1",d1);
ScriptableObject.putProperty(scope,"d2",d2);
ScriptableObject.putProperty(scope,"result",ret);2.设置循环脚本为如下格式
(fucntion(){
var i,len=d1.length<d2.length?d1.length:d2.length;
var $0,$1....;
for(i=0;i<len;i++){
$0=d1[i];
$1=d2[i];
.....
result=$0+$1*2;
}
})();
3.执行脚本
cx.evaluateString(
scope,
buf.toString(),
"<GetResult>",
0,
null
);
这样效率执行一次调用一次高几个数量级,甚至比一般的表达式求值程序还要快很多.
这个帖子没多少人关注,估计会用Rhino的人并不多。
中午找到些资料,打算动态编译编译执行Java,回去测试下
学的够快的,不过用Rhino的话5000个才300ms多一些,BeanShell的效率开来没有Rhino好。
BeanShell不知道支持的是那种语言,我比较懒还是喜欢我比较熟悉的JavaScript
import java.util.Random;import bsh.EvalError;
import bsh.Interpreter;public class Test2 {
public static void main(String[] args) throws EvalError {
final int count = 5000;
final int paramCount = 4;
double[][] datas = new double[paramCount][count];
random(datas);
double[] results = new double[count];
long t0, t1;
String exp = "$0 + $1 * $2 + $3";
t0 = System.currentTimeMillis();
eval(exp, results, datas);
t1 = System.currentTimeMillis();
System.out.println(t1 - t0);
testCorrect(results, datas);
random(datas);
t0 = System.currentTimeMillis();
eval(exp, results, datas);
t1 = System.currentTimeMillis();
System.out.println(t1 - t0);
testCorrect(results, datas);
random(datas);
t0 = System.currentTimeMillis();
eval(exp, results, datas);
t1 = System.currentTimeMillis();
System.out.println(t1 - t0);
testCorrect(results, datas);
}
public static void eval(String expression, double[] results, double[]... datas) throws EvalError {
String ep = expression.replaceAll("\\$(\\d+)", "datas[$1][j]");
String exp =
"for(int j = 0, k = datas[0].length; j < k; j++) {" +
" results[j] = " + ep + ";" +
"}";
Interpreter bsh = new Interpreter();
bsh.set("datas", datas);
bsh.set("results", results);
bsh.eval(exp);
results = (double[])bsh.get("results");
}
/**
* 随机生成数据值
*
* @param datas
*/
private static void random(double[][] datas) {
Random ran = new Random();
for(int i = 0; i < datas.length; i++) {
for(int j = 0; j < datas[i].length; j++) {
datas[i][j] = ran.nextDouble() * datas[i].length;
}
}
}
/**
* 测试调用的结果是否正确
* @param results
* @param datas
*/
private static void testCorrect(double[] results, double[][] datas) {
double[] res = new double[results.length];
for(int j = 0; j < datas[0].length; j++) {
for(int i = 0; i < datas.length; i++) {
res[j] = datas[0][j] + datas[1][j] * datas[2][j] + datas[3][j];
}
}
for(int i = 0; i < results.length; i++) {
if(results[i] != res[i]) {
System.out.printf("error! %.6f --> %.6f%n", results[i], res[i]);
}
}
}
}第二种方法,只是更改 eval 方法,使用字符串更像一个 Java 方法: public static void eval(String expression, double[] results, double[]... datas) throws EvalError {
String ep = expression.replaceAll("\\$(\\d+)", "datas[$1][j]");
String exp =
"public void eval(double[] results, double[][] datas) {" +
" for(int j = 0, k = datas[0].length; j < k; j++) {" +
" results[j] = " + ep + ";" +
" }" +
"}";
Interpreter interpreter = new Interpreter();
NameSpace ns = NameSpace.JAVACODE;
interpreter.eval(exp, ns);
BshMethod method = ns.getMethods()[0];
method.invoke(new Object[]{ results, datas }, interpreter);
}
找到的资料非常让人振奋,不得不说脚本语言是无法和编译语言比速度的。package com.fhd.analysis.data;public interface Calculater {
public void calculate(double[] result,double[][] d);
}
package com.fhd.analysis.test;import java.io.StringReader;
import java.util.Date;import org.codehaus.janino.ExpressionEvaluator;
import org.codehaus.janino.Scanner;import com.fhd.analysis.data.Calculater;public class TestDynamicCimpiel {
public static void main(String[] args){
try{
double[] d0=new double[5000];
double[] d1=new double[5000];
double[] r=new double[5000];
String code="public void calculate(double[] r,double[][] d){"
+" for(int i=0;i<5000;i++){"
+" r[i]=d[0][i]+d[1][i]*2;"
+" }"
+"}"; Calculater c=(Calculater)ExpressionEvaluator
.createFastClassBodyEvaluator(
new Scanner(null,new StringReader(code)),
Calculater.class,
(ClassLoader)null
);
for(int i=0;i<5000;i++){
d0[i]=Math.floor(Math.random()*100);
d1[i]=Math.floor(Math.random()*100);
}
double[][] d=new double[][]{d0,d1};
Date ds=new Date();
c.calculate(r,d);
c.calculate(r,d);
c.calculate(r,d);
c.calculate(r,d);
c.calculate(r,d);
c.calculate(r,d);
Date de=new Date();
System.out.println(
"耗时:"+(de.getTime()-ds.getTime())
);
}catch(Exception e){
e.printStackTrace();
}
}
}耗时:0赶紧去下载吧,有的时候还真得用google搜索一些英文关键字,要不这么好的资料
是无论如何也找不到的
http://www.janino.net/download.html
我也不太懂 表达式求值、编译原理 之类的知识,说白了,我就是一小白。如果楼主有空的话,并且,问题仍没解决的话。
可以看看我的以前写的一点代码,记得是关于表达式求值的。
楼主运行一下,看看效率能否满足您的要求。
地址是:http://blog.csdn.net/preferme/archive/2009/07/20/4365251.aspx
因为即使你的效率再高效,连续执行5000次就要执行5000次调用,
就要完成5000次解析,所以根本不可行,除非你自己写的解析程序
能够模拟循环。所以对于这样大数据量的计算必须解决如何避免多次
解析的问题。而解决办法就是要么用脚本,要么实现Java代码的动态
编译动态执行。我找到的那个类库比较理想,可以实现将字符串编译
为类然后加载使用
String ep = expression.replaceAll("\\$(\\d+)", "datas[$1][j]");
String exp =
"public void eval(double[] results, double[][] datas) {" +
" for(int j = 0, k = datas[0].length; j < k; j++) {" +
" results[j] = " + ep + ";" +
" }" +
"}";
Calculater c = (Calculater) ExpressionEvaluator.createFastClassBodyEvaluator(
new Scanner(null, new StringReader(exp)),
Calculater.class,
(ClassLoader) null
);
c.eval(results, datas);
}
相比较而言 BeanShell 是个 Java 解释器,我优化了一下也需要 Janino 近三倍的时间
至于 JDK 6 中的动态语言或者是 Rhino 之类的在速度上根本就没办法比的
的,人家都可以自己实现编译器了,可是我们还在为花大量时间用于解决这些琐碎的问题
只需要把循环体的表达式进行编译,然后循环调用表达式进行结果求值就可以了。
一次编译,多次求值,循环中的表达式结构,始终不会改变。
但效率不知道有没有Janino高。呵呵。
偷懒一点的话,动态生成个含表达式的jsp文件,让容器帮你编译好了,多方便。
代码在VC下编译执行通过了
执行时间上,函数DynCalc2()是编死的代码的10 - 15倍左右
我怀疑switch语句上浪费了不少条件判断的时间,对汇编不了解,如果能直接jump (*pOpt)到各自的处理中,因该还能提高一些性能废话不说了,上代码
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>//==============================================================================
//
// Define Operator
//#define DATA_1 0x81 // Push Data 1
#define DATA_2 0x82 // Push Data 2
#define DATA_3 0x83 // Push Data 3
#define DATA_4 0x84 // Push Data 4
#define DATA_5 0x85 // Push Data 5
#define DATA_6 0x86 // Push Data 6
#define DATA_7 0x87 // Push Data 7
#define DATA_8 0x88 // Push Data 8#define OPT_ADD 0x01 // + (Pop 2 Data, Push Result)
#define OPT_SUB 0x02 // - (Pop 2 Data, Push Result)
#define OPT_MUL 0x03 // * (Pop 2 Data, Push Result)
#define OPT_DIV 0x04 // / (Pop 2 Data, Push Result)#define OPT_DUP 0x11 // Dup Data (Pop 1 Data, Push 2 Copy)
#define OPT_SWAP 0x12 // Swap Data (Pop 2 Data, Swap Data, Push 2 Data)#define OPT_END 0xFF // Pop 1 Data, Return//==============================================================================
//
// Function
//__forceinline DOUBLE WINAPI DynCalc(BYTE * pOpt, DOUBLE * pData)
{
DOUBLE dStack[1024] = { 0 };
DOUBLE * pStack = dStack; DOUBLE dTemp; for (;;)
{
switch (*pOpt)
{
case DATA_1: *pStack = pData[0]; pStack++; break;
case DATA_2: *pStack = pData[1]; pStack++; break;
case DATA_3: *pStack = pData[2]; pStack++; break;
case DATA_4: *pStack = pData[3]; pStack++; break;
case DATA_5: *pStack = pData[4]; pStack++; break;
case DATA_6: *pStack = pData[5]; pStack++; break;
case DATA_7: *pStack = pData[6]; pStack++; break;
case DATA_8: *pStack = pData[7]; pStack++; break; case OPT_ADD: pStack--; *(pStack - 1) += *pStack; break;
case OPT_SUB: pStack--; *(pStack - 1) -= *pStack; break;
case OPT_MUL: pStack--; *(pStack - 1) *= *pStack; break;
case OPT_DIV: pStack--; *(pStack - 1) /= *pStack; break; case OPT_DUP:
{
*pStack = *(pStack - 1);
pStack ++;
break;
}
case OPT_SWAP:
{
dTemp = *(pStack - 1);
*(pStack - 1) = *(pStack - 2);
*(pStack - 2) = dTemp;
break;
}
case OPT_END:
return *(pStack - 1);
} pOpt++;
}
}
VOID WINAPI DynCalc2(DWORD dwLoop, BYTE * pOpt, DOUBLE * pData, DOUBLE * pResult)
{
DOUBLE dStack[1024] = { 0 };
DOUBLE * pStack = dStack;
BYTE * pRawOpt = pOpt; DOUBLE dTemp; for (;;)
{
switch (*pOpt)
{
case DATA_1: *pStack = pData[0]; pStack++; break;
case DATA_2: *pStack = pData[1]; pStack++; break;
case DATA_3: *pStack = pData[2]; pStack++; break;
case DATA_4: *pStack = pData[3]; pStack++; break;
case DATA_5: *pStack = pData[4]; pStack++; break;
case DATA_6: *pStack = pData[5]; pStack++; break;
case DATA_7: *pStack = pData[6]; pStack++; break;
case DATA_8: *pStack = pData[7]; pStack++; break; case OPT_ADD: pStack--; *(pStack - 1) += *pStack; break;
case OPT_SUB: pStack--; *(pStack - 1) -= *pStack; break;
case OPT_MUL: pStack--; *(pStack - 1) *= *pStack; break;
case OPT_DIV: pStack--; *(pStack - 1) /= *pStack; break; case OPT_DUP:
{
*pStack = *(pStack - 1);
pStack ++;
break;
}
case OPT_SWAP:
{
dTemp = *(pStack - 1);
*(pStack - 1) = *(pStack - 2);
*(pStack - 2) = dTemp;
break;
}
case OPT_END:
{
*pResult = *(pStack - 1); pResult ++;
pStack --; pOpt = pRawOpt;
dwLoop --; if (dwLoop == 0)
return; continue;
}
} pOpt++;
}
}int main()
{
DOUBLE dP1 = (DOUBLE)rand() / 600.0;
DOUBLE dP2 = (DOUBLE)rand() / 601.0;
DOUBLE dP3 = (DOUBLE)rand() / 602.0;
DOUBLE dP4 = (DOUBLE)rand() / 603.0;
DOUBLE dResult; DWORD dwLoop = 1000000; BYTE btOpt[] = {DATA_1, DATA_2, DATA_3, OPT_MUL, DATA_4, OPT_DIV, DATA_5, OPT_MUL, OPT_ADD, OPT_END};
DOUBLE dData[] = {dP1, dP2, 2.0, dP3, 2.5}; DOUBLE * pData2 = (DOUBLE *)malloc(dwLoop * sizeof(dData));
DOUBLE * pResult = (DOUBLE *)malloc(dwLoop * sizeof(dResult)); DWORD s, e;
DWORD i; {
DOUBLE * pTemp = pData2; for (i = 0; i < dwLoop; i++)
{
memcpy(pTemp, dData, sizeof(dData));
pTemp += (sizeof(dData) / sizeof(DOUBLE));
}
}
//==============================================================================// s = GetTickCount();
//
// for (i = 0; i < dwLoop; i++)
// {
// dResult = DynCalc(btOpt, dData);
// }
//
// e = GetTickCount();
// printf("%f, %d\n", dResult, e - s);//============================================================================== s = GetTickCount(); DynCalc2(dwLoop, btOpt, pData2, pResult); e = GetTickCount();
printf("%f, %d\n", *pResult, e - s);//============================================================================== s = GetTickCount(); for (i = 0; i < dwLoop; i++)
{
dResult = dP1 + dP2 * 2.0 / dP3 * 2.5;
}
e = GetTickCount();
printf("%f, %d\n", dResult, e - s);//============================================================================== return 0;
}
Forcal一级函数的计算速度约为(C/C++)或FORTRAN速度的50%左右,二级函数的速度稍有降低。FORCAL与VC的速度比较:表达式:(x,y)=cos{1-sin[1.2*[x+0.1]^(y/2-x)+cos{1-sin[1.2*[x+0.2]^(y/3-x)]}]-cos
{1-sin[1.2*[x+0.3]^(y/4-x)]}-cos{1-sin[1.2*[x+0.4]^(y/5-x)+cos{1-sin[1.2*[x+0.5]
^(y/6-x)]}]-cos{1-sin[1.2*[x+0.6]^(y/7-x)]}}}FORCAL正对表达式进行循环求和计算,请等待... ...
forcal计算结果:19160.536601703152 运行时间:1782 即: 1.782秒VC++ 正对表达式进行循环求和计算,请等待... ...
VC++ 计算结果:19160.536601703152 运行时间:1328 即: 1.3280000000000001秒我的blog:http://blog.csdn.net/forcal
z=0.0; old=clock();
for(x=0.0;x<=1.0;x=x+0.0011)
for(y=1.0;y<=2.0;y=y+0.0011)
{
z=z+cos(1.0-sin(1.2*pow(x+0.1,y/2.0-x)+cos(1.0-sin(1.2*pow(x+0.2,y/3.0-x))))-cos(1.0-sin(1.2*pow(x+0.3,y/4.0-x)))-cos(1.0-sin(1.2*pow(x+0.4,y/5.0-x)+cos(1.0-sin(1.2*pow(x+0.5,y/6.0-x))))-cos(1.0-sin(1.2*pow(x+0.6,y/7.0-x)))));
}
now=clock();
cout<<"VC++ 计算结果:"<<setprecision(20)<<z<<' ';
cout<<" 运行时间:"<<now-old<<" 即: "<<(double)(now-old)/CLOCKS_PER_SEC<<"秒"<<endl<<endl;
我们的问题了。谢谢你提供的信息,我会把他推荐给我的搞c++的同学。因为一直做java Web开发,关注c++
很少,看了下相关的介绍,那个库确实是个好东西。可惜我们用不上,一是因为我们的项目还没有复杂到那个
程序,自己写个动态编译的Java程序足以应付了,再有就是我们的团队里没有人熟悉如何用JNI调用c++,而且
那个库是需要依赖Windows的,这一点也不能不考虑。