[讨论] Java与C/C++的性能对比
IcyFenix
2011-02-14
我回来了,今天日子特殊,虽然请假在家,但是没有连续时间写字,一直拖到现在:) ,我们继续这个话题。
首先,JIT中做逃逸分析是栈上分配的基础,但不意味着有逃逸分析,就有栈上分配了,只是完成了其前提而已。你文档中也说了,栈上分配现在的状态是可以做,但事实上还没有做。 其次,根据现有的使用报告看来,逃逸分析花费的代价并不比获取的收益小多少,花了力气分析,最后的RP不好,没找到太多确定不会逃逸的对象,虚拟机表示鸭梨很大。因此虽然逃逸分析日后肯定很有前途,但至少目前为止貌似还不太靠谱,大家反应的效果是“microbenchmarks中能跑出很好看的成绩,但实际应用,尤其是规模较大的应用中效果貌似不太好”,否则不会连C2都默认关闭它,也不会在6u18中被完全关闭掉。关于这点的论据我没有数据支持,比较心虚 ,撒迦不妨详细说一说。 再次,哪怕JIT中栈上分配的优化实现了,并且是比较靠谱的实现,我相信付出的代价也与C/C++中的不能同日而语,如你前面所说的,有些语言特性天生就决定了它比较有“慢”的潜质,C/C++中对象内存布局的简单及执行子系统的简单,在这方面能获取到一点收益。 最后,抛开技术层面不谈,提供代码上可控的内存分配方式,至少在效率导向上总能发挥一定好处的:编译器去判断一个对象是方法私有的然后去栈上分配,与因为这个对象使用了栈上分配所以程序员在代码中注意保持方法私有,我相信后者写出来的代码一般来说能榨取出更多运行效率(当然,可能程序员会更累) 最最后,2月14日晚应该出去过节了吧? |
|
RednaxelaFX
2011-02-14
wkoffee 写道 还有lz说的inline,这个对优化的重要性无可比拟,我认为java在这方面也有优势。c compiler一般以一个c文件作为一个编译单元,只能在自己可见的范围内进行inline,所以绝大部分被inline的函数需要放在头文件中,这其实要很大程度上要依赖程序员的经验。java的jit compiler可以根据实际的运行情况决定如何inline,这个不需要程序员介入而且实际的效果更好。
嗯,这方面Java与C各自的特性使得它们在不同的方面适合优化。 C的话,编译器有机会做link-time optimization,像VC可以做link-time code generation (LTCG),这样就能在源码边界间也实现优化了。这样算是实现了一种whole program optimization。 |
|
IcyFenix
2011-02-14
wkoffee 写道 我也来为java说两句,java对于c/c++来说还有一个优势就是类型安全,这对别名分析(alias analysis)带来很大的好处。c语言中的数据类型可以任意转换,对于compiler来说就是一场噩梦。例如一个简单的例子
void foo( ClassA a, ClassB b ){ x=a.f1; b.f2=0; y=a.f1; ... 假设ClassA和ClassB没有继承关系,这里java的compiler可以安全的认为a和b指向的是不同的内存,y可以直接使用x的结果,或者reorder前两句的顺序都可以由compiler根据需要决定。如果换成c语言中的指针,虽然在绝大部分情况下这两块内存不重叠,但compiler必须采用最保守的策略。 还有lz说的inline,这个对优化的重要性无可比拟,我认为java在这方面也有优势。c compiler一般以一个c文件作为一个编译单元,只能在自己可见的范围内进行inline,所以绝大部分被inline的函数需要放在头文件中,这其实要很大程度上要依赖程序员的经验。java的jit compiler可以根据实际的运行情况决定如何inline,这个不需要程序员介入而且实际的效果更好。 欢迎新同学进来。 第一个例子,jit是可以比静态编译器多做一次Copy Propagation优化,但以这点来证明类型安全在运行效率上也是java的优势会不会太牵强了?需要证明这点,必须拿出保证类型安全付出的代价会比保证类型安全所带来的收益小才行。 关于内联,顶楼中提出的论点是“由于java鼓励的面向对象编程,导致java虚方法使用频率远多于c++”,而不是说jit的内联优化比c++的差,事实上jit的内联应当说是相当先进,即使是虚方法jit也可以通过CHA来找出可以内联的那一部分虚方法来内联。但是这是一个开发方式导向上的问题,而不是技术上的问题,如你写了一个实体类,本来这种类不应当会有什么子类,而且都是方法调用成本大于代码执行成本,最适合内联的。但是搞不好引入Hibernate,立刻就给你动态生成了一堆子类型,jit访问的时候就迷茫了。 |
|
IcyFenix
2011-02-14
wkoffee 写道 对于implicit null check(INC),还有些隐含的性能损失,它导入了一些新的control dependent,而且sun的c2对于含有INC的basic block有一些特殊的约定(具体为什么不清楚),我们的jit compiler下一步打算去掉这个优化,恢复explicit null check,使用其他的技术来达到它的效果。
来了一个开发jit编译器的大牛?还是“我们的”是指sun hotspot? |
|
RednaxelaFX
2011-02-14
IcyFenix 写道 首先,JIT中做逃逸分析是栈上分配的基础,但不意味着有逃逸分析,就有栈上分配了,只是完成了其前提而已。你文档中也说了,栈上分配现在的状态是可以做,但事实上还没有做。
现在HotSpot基于EA做的是scalar replacement,暂时没有做stack allocation。要比较两者以及其它分配方式的话,scalar replacement俨然是最好的,真正能把一些伪依赖给消除掉,并且也更有利于寄存器分配。 概念上说,scalar replacement只适合方法作用域内完全不逃逸的情况,而stack allocation的限制稍微宽一些,只要线程作用域内不逃逸就好。至于为啥现在HotSpot没选择去实现它,hmm,这个我不清楚。 专家Gil Tene是这么说的: Gil Tene 写道 The GC efficiency afforded by Stack Allocation may [negatively] surprise most people. Eliminating allocation all-together through in-frame escape analysis (e.g. turning objects into registers) is one thing - it gives you faster programs, regardless of GC issues. However, if you actually have to allocate the thing in memory and in an object structure (which is generally the case for cross-frame situations), placing it in a thread-local area or a stack arrangement turns out to be a very small win, and if the VM is not careful about it, may often not be worth the extra processing overhead compared to bump-a-pointer contiguous heap allocation. The reason behind this is that generational collection of short lived stuff is already super-efficient, and most stack-allocatable objects are exactly the sort of objects that generational GC spends zero time on: short lived enough that they are never visited by the collector and never copied anywhere, not even to a survivor space. (read: no cost at all, except for a somewhat earlier trigger of a new-gen collection cycle through increased allocation pressure). So, if you already have to pay for allocation in one form or another, generational collection of most stack-allocatable objects is actually cheaper than dealing with stack "de-allocation" (or alternate forms of per-frame bookkeeping) of object allocation frames. Escape analysis’s relative, which we dub "escape detection" (and for which we have hardware assists in Azul machines), allows us to evaluate allocation behaviors and opportunities across frames, and can offer interesting optimizations, but the resulting GC efficiency is not nearly as big as a win as you may think. At Azul, we simply eliminate or change the question to begin with: our GCs don't stop your program, and use plentiful cores for background execution while your program threads keep executing happily. We eat GC pressure for breakfast, as in tens of GBytes per second of sustainable pauseless allocation with GC happily keeping up in the background. Taking pressure off such a collector is simply not so much an issue or a goal any more, so you can make a really big mess if you want, and focus on making your program do interesting, useful stuff... -- Gil. Azul Systems
他所在的Azul公司的改进版HotSpot或许是现在所有基于Sun HotSpot的JVM中最高效的。于是还是有参考价值的。 IcyFenix 写道 其次,根据现有的使用报告看来,逃逸分析花费的代价并不比获取的收益小多少,花了力气分析,最后的RP不好,没找到太多确定不会逃逸的对象,虚拟机表示鸭梨很大。因此虽然逃逸分析日后肯定很有前途,但至少目前为止貌似还不太靠谱,大家反应的效果是“microbenchmarks中能跑出很好看的成绩,但实际应用,尤其是规模较大的应用中效果貌似不太好”,否则不会连C2都默认关闭它,也不会在6u18中被完全关闭掉。关于这点的论据我没有数据支持,比较心虚 ,撒迦不妨详细说一说。
所有带有tradeoff性质的东西,如果拿cost-benefit model来看,肯定有所谓的break-even point,在那之上会有收益区。到底要不要做某种优化要看预计的收益够不够大。 Sun的HotSpot很早就开始关注以及实现EA,但多年来效果一直不好。它的server编译器实现的EA所依赖的原始论文是1999年IBM发的一篇名为Escape Analysis for Java的论文。 HotSpot、JRockit与IBM的几代JVM都多少采用了相似的方式来实现EA,不过HotSpot貌似是最迟真正将它实用化的orz 一直困扰最大的地方是HotSpot实现的EA比较不稳定,简单来说就是bug多多。不过从JDK6u23开始-XX:+DoEscapeAnalysis已经是默认开启的了。在6u18的时候暂时禁用掉貌似是因为当时发现了一些不稳定的地方,而且要做改进实现些新东西所以干脆对外禁用了。 我看到的资料也是说当前的EA技术的scalability不太好。为此,有些有趣的、较低成本的EA正在成型。例如说先做很低成本的、粗略的估计,看EA是否有可能成功,如果有则再使用完全的EA去分析。另外也有像JRockit可以对方法中的一部分做EA。 看不到源码无法做比较,不过我一直是猜想JRockit的EA比HotSpot的靠谱。反正那两组人现在也已经变成一组了,HotSpot肯定也能吸取不少好东西过来吧。 作为参考资料,放个链接:AMD Developer Central: How JVMs use Escape Analysis to Improve Application Performance 我还没试过在比较大的规模观察HotSpot、JRockit与J9的EA的效果,手上缺少很坚实的第一手资料,所以也不敢多说。说多错多 |
|
RednaxelaFX
2011-02-14
IcyFenix 写道 第一个例子,jit是可以比静态编译器多做一次Copy Propagation优化,但以这点来证明类型安全在运行效率上也是java的优势会不会太牵强了?需要证明这点,必须拿出保证类型安全付出的代价会比保证类型安全所带来的收益小才行。
这里你就很明显没看清楚人家例子的用意了。CSAPP里对alias问题举例讲得不错,去读读? 这个例子最明显的地方是a.f1与b.f2在没有类型安全的保障下很有可能实际上指向了同一个存储空间,也就是形成了别名。这样编译器就不能将x的值直接用在a.f1第二次出现的地方了,因为a.f1的定义此时可能被前面的b.f2的赋值给“kill”了。 IcyFenix 写道 关于内联,顶楼中提出的论点是“由于java鼓励的面向对象编程,导致java虚方法使用频率远多于c++”,而不是说jit的内联优化比c++的差,事实上jit的内联应当说是相当先进,即使是虚方法jit也可以通过CHA来找出可以内联的那一部分虚方法来内联。但是这是一个开发方式导向上的问题,而不是技术上的问题,如你写了一个实体类,本来这种类不应当会有什么子类,而且都是方法调用成本大于代码执行成本,最适合内联的。但是搞不好引入Hibernate,立刻就给你动态生成了一堆子类型,jit访问的时候就迷茫了。
与其说Java是“鼓励面向对象编程”而不如说是鼓励late binding。同样很鼓励面向对象编程的C#就选择让方法默认是非虚的。 另外,具体调用目标的判定也不是只有CHA一种手段。事实上HotSpot的server编译器还可以依靠别的推断方式。请参考Cliff Click这篇文章:Inline Caches and Call Site Optimization |
|
IcyFenix
2011-02-15
RednaxelaFX 写道 IcyFenix 写道 第一个例子,jit是可以比静态编译器多做一次Copy Propagation优化,但以这点来证明类型安全在运行效率上也是java的优势会不会太牵强了?需要证明这点,必须拿出保证类型安全付出的代价会比保证类型安全所带来的收益小才行。
这里你就很明显没看清楚人家例子的用意了。CSAPP里对alias问题举例讲得不错,去读读? 这个例子最明显的地方是a.f1与b.f2在没有类型安全的保障下很有可能实际上指向了同一个存储空间,也就是形成了别名。这样编译器就不能将x的值直接用在a.f1第二次出现的地方了,因为a.f1的定义此时可能被前面的b.f2的赋值给“kill”了。 Hmmm……是前面这句有问题? IcyFenix 写道 第一个例子,jit是可以比静态编译器多做一次Copy Propagation优化
优化前: void foo( ClassA a, ClassB b ){ x=a.f1; b.f2=0; y=a.f1; }优化后: void foo( ClassA a, ClassB b ){ x=a.f1; b.f2=0; y=x; //Copy Propagation }这个优化在Java中能做,但是在C中不能做,原因是C中无法保证a.f1与b.f2指向的不是同一块地方,也就是无法保证给x赋值的a.f1与给y赋值时候的a.f1的值是一样的,不符合复写传播的条件。这个我觉得和你说的是同一回事呀,还是你想强调的本意是指别名问题是因,复写传播是果? 抑或,是不同意后面这句? IcyFenix 写道 但以这点来证明类型安全在运行效率上也是java的优势会不会太牵强了?需要证明这点,必须拿出保证类型安全付出的代价会比保证类型安全所带来的收益小才行。
|
|
RednaxelaFX
2011-02-15
IcyFenix 写道 Hmmm……是前面这句有问题?
引用 第一个例子,jit是可以比静态编译器多做一次Copy Propagation优化 这个叫CSE(common sub-expression elimination)好不… IcyFenix 写道 还是你想强调的本意是指别名问题是因,复写传播是果?
嗯,前面还要加上因为有类型保证所以才能证明没有别名问题。 我的印象是编译器优化里很头疼的一部分就是point-to analysis,或者叫pointer analysis。Java的类型规定比起C或C++要容易分析许多。 如果一个JVM实现选择使用直接指针来实现Java级别引用(例如32位HotSpot的做法),那么一个很有趣的地方就是,所有用户代码里的引用都只可能成为指向对象起始位置的指针,而不可能指向一个对象的内部,因为Java不允许这样的语义。 那么如果JVM选择在自身的实现中为了快而在部分地方使用指向对象内部的指针(称为derived pointer,例如HotSpot的server编译器会这么做),编译器可以知道所有derived pointer都在它的控制之下而不可能是用户造出来的,这样也提供了更强的保证来辅助优化。 |
|
wkoffee
2011-02-15
隔了一天好像话题又变了很多,不知道怎样插嘴了。
关于别名的例子只是想说明java在这方面有优势,c由于类型过于灵活有时会失去一些优化机会。所以新的c标准引入restrict关键字来帮助compiler,但一般的程序员好像还不清楚它的作用。 escape analysis我们用hotspot做过一些测试,实际的效果一般,印象中只有一项测试有提高,加权平均一下最后的分数基本没有提高。不知道是sun实现的问题还是实际就是这样。 |
|
RednaxelaFX
2011-02-15
我也就随便乱扯而已…请楼上有空的话多指点
|
相关讨论
相关资源推荐
- 高通QMI拨号工具
- centos下qmail安装配置
- QMAIL+mysql for ;LINUX_MAILSERVER
- centos安装qmail_深入研究QMail
- qmail简单安装
- Qmail+vpopmail+daemontools+ucspi邮件系统安装及其SMTP认证配置
- 搭建Qmail邮件系统(环境篇)
- qmail + webmail on Linux9 安装全过程
- Cacti Templates (Cisco Routers and Switches,Mysql,postfix,Qmail,Apache,Widows and many more)
- [精华] qmail安装心得(安装过程)