五千年(敝帚自珍)

主题:【原创】Java Fantasy 漫谈 -- Highway

共:💬47 🌺62
全看分页树展 · 主题
家园 【原创】Java Fantasy 漫谈

前言

期待了一个赛季,今天晚上总算是爽了一把。火箭队主场狂胜达拉斯小牛队(31分之狂胜)。狂胜不说,更爽的是吾友姚明大放异彩,出手16次,砍下36分(go figure)。用休斯敦解说员的话就是“小牛队整个给blow了, what the heck of a job Rockets have done”。大家如果转载的话,一定不要弄错了,如果搞成“what the heck of a blow job Rockets have done”那就麻烦了。

这一高兴呢,就喝了几杯。这一喝高了呢,就想说点什么。用吾友李大嘴的话说就是“你会啥就用啥招呼呗”。兄弟我不比关中大侠吕轻侯,诗是写不出来的,所示只好说点程序啥的了。

好了,言归正传,说说我对JAVA的几点幻想吧,就是所谓的“Java Fantasy”。

说到JAVA呢,人们总忘不了提起性能的问题。网上有一个公开的小程序,叫做scimark2,主要是看看JAVA在数值计算方面表现如何。那个程序我下载了并把玩了一下,对比了一下C, JAVA和C#在这方面的表现如何。这个scimark2太过简单,作为benchmark程序是不太合适的。不过多少还是有些借鉴意义的。结果呢,用VC2005编译的C程序表现最好,Server JVM下的JAVA次之,大约和C程序有10%的差距,C#更差劲一些,比C code差了30%左右(不过呢,如果用VC6.0编译那段C code,其结果最差,跑得最慢)。

这个结果和我预计的差不多,Java比C#快可能是由于以下几个原因:

1)Java的primitive data type比.NET中的value type可能更精干一些(sorry,尚没有找到证据,还处于瞎猜状态)

2)Java的Hot spot虚拟机比.NET的JIT虚拟机有更好的优化机能。JIT虚拟机是一次性的将manage code转化成native code,运行次数再多也不会有机会进一步优化native code;而Java的Hot spot虚拟机不断地跟踪和调整程序,进过一段时间后,他可能会跟据程序具体的执行特点做出做好的优化。这一点比较好证明,如果你使用Java的client Hot spot虚拟机比,那么程序执行的就不如.NET快,原因就在于client Hot spot虚拟机主要侧重于程序启动速度和内存占用方面的优化,和server Hot spot虚拟机有所不同。当然要指出的是server Hot spot虚拟机要进过一段时间才能“渐入佳境”,不像.NET那样“从一而终”,性能重头到位都一个样。

于是乎,我脑海中大概就有了这样一个概念“就纯数值计算而言,Java要比.NET快一些”。

这个结论似乎比较稳妥,直到有一天我老人家给blow了。

事情是这样的。工作中有些金融方面的计算,手边有现成的Java code。我老人家一高兴,就把程序拿到了VS2005了,用Visual J#编译了一把。由于都是数值计算,没有新的Fancy的Java feature,所以Visual J#没有问题通过了编译。一运行,“我的个神呀”,比Java快了50%左右。

Damn it, what the hell is going on here? .NET中的二等公民Visual J#怎么会如此牛掰?正待我想进一步调查原委的时候,发现Visual J#非常不稳定,经常半途就尥蹶子了。本来想给微软反映一下,后来一想算了吧,反正Visual J#在.NET中也就是个摆设,没人把它当颗葱,拿它蘸酱吃。

我老人家一时兴起,把那整个library给转化成C#程序了(其实很简单,没用了20分钟)。然后再一运行,“亲娘咧”,C#牛X啊,Beat Java没有一点商量,硬生生地快出去50%左右。

这下子问题来了,数值计算到底是Java快呢还是.NET快?

为了把这个问题搞清楚,我老人家一头扎进了程序里,想看出来个所以然来。经过一番功夫,我初步确定了嫌疑对象,那就是几个scimark2中不曾调用的函数,比如pow, exp, sqrt, log等等。这些函数在我的libray中调用比较频繁。

把这几个函数单拎出来,Java和C#各算个几千万次,结果一看还真是C#遥遥领先(sqrt除外)。好了这下问题清楚了,那就是一般的加减乘除,Java还是比较利索的,但是复杂一些的计算,也就是Math class里的那些函数写的不怎么样,拖了Java的后腿。后来google了一下,发现Java Math里面的三角函数更加蹩脚,我粗粗算了一下比.NET要慢2到3倍。

怎么会是这样呢?我把我这个发现告诉了我的一个同事(MIT的博士呦),他老人家的第一反应就是“微软一定cheat了,他们肯定在.NET中偷偷使用native code了!

我老人家于是又一头扎进了.NET的library里面,把微软的code反编译一看,真相大白,微软果然是使用的native code。

呸,无耻,微软真不要脸”,看到这里大家一定会这么想。为了避免草率下结论,有老人家又一头扎紧了Java的library里面,Java比较好,source code和JDK一道发布了,看起来很容易。不过为了保险起见,我还是反编译了rt.jar文件里面的class。结果你们猜怎么的,Java一样也使用的是native code。Math class调用StrictMath class,而StrictMath则赤裸裸的调用native code了。

看来Java和.NET半斤八两,彼此彼此。乌鸦用不着笑猪黑,哈哈哈哈。。。

且慢,你老人家先别笑,问题还没有搞清楚呢,都使用native code,那为什么微软的code要快好多呢?

问得好,这个问题看起来有些蹊跷。这些pow, exp, log数学问题又不是什么high-tech,都多少年了,那些算法大家都清楚,我不认为微软有什么独门秘笈。

是不是微软调用native code的方法(p/invoke)比Java的jni效率更高,损耗更小呢?

几个简单的测试下来表明从.NET里调用native code和从Java里面调用native code基本上差不多,没有什么明显区别。

“乖乖,那问题到底出在哪里?”

没办法,还得靠google了。经过一番查询,再加上自己的思考,基本上认为问题出在了StrictMath上。

从Java 1.4开始,Sun引进了StrictMath这么个东西,其初衷就是不管在什么样的硬件平台上,什么样的OS下,Java的数值计算结果要保持高度的一致,都要符合所谓新的IEEE的标准。结果呢,Sun选择了自由软件fdlibm库函数来作为Java的native code。

事实上呢,这个StrictMath“束缚”不小,Java 1.4以前不理会这个茬儿,数学计算就是快。比如说Java 1.3.x就比Java 1.4/1.5快。当然了,Java是跨平台的,为了“准确性”牺牲一些性能是可以理解的。

反观微软呢,他没有这样的束缚,所以可以集中精力把x86平台的code搞好就行。另外微软在PC平台上经营了多年,他编译出来的code还真的可能比fdlibm要快一些。

问题是,很多场合下,我们不需要那么样的“准确性”,因为不是每个人都在乎跨平台问题,我们更需要的是性能,在指定平台上的性能,比如在x86平台上,在Windows环境下。

最简单的办法是开发自己的一套Math lib,然后再程序里动用自己的Math lib,而不是Java的Math。趁着今天我高兴,就连具体实施细节也一并和盘端出吧,你老可记好了。

1)把Math和StrictMath的原程序从src.zip里拎出来。

2)把packge改成你想要的,比如说packge com.xxx.mylib

3)编译这两个文件,然后用javah编译StrictMath.class,生产StrictMath.h头文件

4)写一个简单的C程序,实现StrictMath.h的那些native函数。注意,我没让你自创一套算法,你直接调用微软的库函数就可以了,比如cos函数这样一行就可以了(中间那行)

JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_cos(JNIEnv *env, jobject obj, jdouble value)

{

return(cos(value));

}

5)将这个C程序编译成DLL,比如说名字叫作xxx.dll。

6)改一下StrictMath,加以一段小code

static

{

System.loadLibrary("xxx");

}

好了,到这里,新一代的库函数就诞生了。调用的语法和原先使用Java Math的一模一样,唯一的区别是import你的com.xxx.mylib.Mathclass,而不是default java.lang.Math。

当然你老人家要是想更干净一些,连StrictMath都可以省略,直接在Math里使用native code就行了。我上面的法子只是想省你一些时间,不过多了一层re-direction,性能上可能会微微损失一些。

到这里,问题解决了,但不是很爽,因为你要指明调用你自己的库函数,而不是Java的。对于已有的程序,你要修改原程序(虽然只是修改import部分)。所以我老人家认为最好的解决方案是(以Windows平台为例) :

开放StrictMath class。不要象现在那样,明明使用了native code,却不像标准的JNI那样指明载入哪一个DLL。如果像我那样指明的话(System.loadLibrary("xxx");),那么我们可以用自己的DLL覆盖Java缺省的DLL,或是JVM首先试图载入当前路径下的用户自己的DLL,如果没有的话,再载入系统缺省的DLL。这样一来,用户可以更具自己的需要决定是不是要提供自己的DLL。

对于99%的Java用户来说,可能他们根本不在乎这个问题,那么好,一切都跟没有发生一样。对于那么1%的用户,则有自由选择的空间了。往小里说,可以用比较快的lib取代Java的缺省函数,往大里说,可以用专门的协处理器,让native code指挥专用协处理器来完成这样的函数。我看到网上有些公司已经提供软件包,让你用Video card的GPU来完成很多的数学计算,达到10-20倍的性能提升。想想如果你的native cod是使用这样的软件包卡发出来的,那么你的Java程序岂不是可以fly了吗?而可贵的是你的Java程序没有一点特殊之处,换个环境照样运行,唯一的区别只是慢一些,程序不需要改动一行。

这就是我对Java的第一个幻象。其他幻象且听以后分解。

关键词(Tags): #Java#Math#StrictMath#C#元宝推荐:闲看蚂蚁上树,四月一日,

本帖一共被 1 帖 引用 (帖内工具实现)
全看分页树展 · 主题


有趣有益,互惠互利;开阔视野,博采众长。
虚拟的网络,真实的人。天南地北客,相逢皆朋友

Copyright © cchere 西西河