- 浏览: 3016642 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
今天有朋友提到一个叫 ReflectASM的库,为Java环境提供高性能的反射操作支持。它的实现方式是动态代码生成。
以前我的一篇日志里写过,Oracle/Sun JDK6的反射方法调用的实现当中重要的一环就是动态代码生成。
但是今天的主角不是ReflectASM,而是Oracle/Sun JDK里的sun.misc.Unsafe类,以及这个类上的getInt(Object, long)方法:
(注意:sun.misc.Unsafe是Oracle/Sun JDK里的私有实现,不是Java标准库的共有API,请权衡好利害关系后再使用。)
朋友利用Unsafe.getInt()来实现了快速的反射访问字段的功能,写了这么篇日志:
Java 反射调用的一种优化
在文章末尾,他提到直接用Unsafe.getInt()与用普通反射访问字段的性能对比,
于是问题就来了:在我的机器上也是快三倍么?为什么“快了三倍”?是什么跟什么比较快了三倍?
把原本的测试代码拿来:(有细微改动)
在我的测试环境里跑:
这时间看起来不像是“三倍”,而更像是哪里出岔子了
那么在揭开谜底前,我们来从侧面看看能不能有啥发现。
我们已经知道上面对比的双方是Unsafe.getInt()和Field.get()。Unsafe.getInt()是个native方法,就算是到头了。
实际上Unsafe.getInt()方法在HotSpot VM里是个intrinsic方法,有特别的优化处理。欲知详情可以参考HotSpot server compiler的LibraryCallKit::inline_unsafe_access()函数,它可以将原本的Unsafe.getInt()方法调用内联到调用点上,彻底削除JNI函数调用开销。
但想必这点优化没啥影响,大家是公平的?后面再继续说。
在Oracle/Sun JDK6里,Field.get()又是怎样实现的呢?
跟随Field、ReflectionFactory、UnsafeFieldAccessorFactory、UnsafeFieldAccessorImpl、UnsafeIntegerFieldAccessorImpl这些类的相关方法的实现,可以发现当我们要通过反射获取一个int类型的字段时,最终在UnsafeIntegerFieldAccessorImpl也是通过Unsafe.getInt()来干活的:
也就是说,Oracle/Sun JDK6虽然用动态生成代码的方式来实现反射调用方法,但在反射访问字段上却选择了不动态生成代码而直接使用Unsafe这样的native后门。
那么,自己直接使用Unsafe.getInt()比Field.get()占优的地方就在于前者很直接而后者经过了若干层间接/包装,所以前者比后者快。这足以上面看到的测试时间的差异么?
现在让我们来看看事情的真相。
换用Oracle JDK 6 update 25 build 03 fastdebug版,通过-XX:+PrintOptoAssembly来观察JIT编译器生成出来的代码的样子:
可以看到测试里的ReflectionCompare.testUnsafe()方法中的主要计时循环生成为这样的代码:
换回等价的Java代码来表达,也就是:
但原本测试代码里写的是:
而这正是本篇日志的标题所说的:别测空循环。
连被测的对象都被优化掉了的话,这种microbenchmark就彻底没意义了。
前面已经提到,Unsafe.getInt()是HotSpot VM的一个intrinsic方法,因而它的所有特性都是VM清楚了解的,包括有没有副作用、是否能安全的内联、内联后是否可以进一步优化直至完全削除,等。
所以说不要小看了某个native方法的优化对microbenchmark的影响。
用同样办法观察原本测试里的ReflectionCompare.testIntCommon()方法,能看到整个调用过程,包括unsafe.getInt()被内联后的逻辑,都还完好的存在于JIT编译生成的代码中。
在这种条件下比较两边的性能,自然是毫无参考价值可言。
当然,我们还是能得到一些收获。
这个测试里,两边最终干活的都是Unsafe.getInt(),但直接调就优化掉了,而通过Field.get()来调就没被优化掉。
这正好体现了通用型API的多层繁复的封装带来的问题——过多的封装引来了过多间接层,间接层多了之后编译器要优化就会很吃力。
想像一下,如果有人发现直接调用Unsafe.getInt()方法字段的速度很快,然后好心的想把它封装成通用的反射库提供给大家用,很可能发生的状况就是他又把间接层堆积了起来,结果最终用户用的时候就跟直接用反射差不多。
话说回来,一般程序员写Java程序的时候也无需为这种问题纠结就是了。这篇故事看了笑笑就好
以前我的一篇日志里写过,Oracle/Sun JDK6的反射方法调用的实现当中重要的一环就是动态代码生成。
但是今天的主角不是ReflectASM,而是Oracle/Sun JDK里的sun.misc.Unsafe类,以及这个类上的getInt(Object, long)方法:
引用
/** * Fetches a value from a given Java variable. * More specifically, fetches a field or array element within the given * object <code>o</code> at the given offset, or (if <code>o</code> is * null) from the memory address whose numerical value is the given * offset. * <p> * The results are undefined unless one of the following cases is true: * <ul> * <li>The offset was obtained from {@link #objectFieldOffset} on * the {@link java.lang.reflect.Field} of some Java field and the object * referred to by <code>o</code> is of a class compatible with that * field's class. * * <li>The offset and object reference <code>o</code> (either null or * non-null) were both obtained via {@link #staticFieldOffset} * and {@link #staticFieldBase} (respectively) from the * reflective {@link Field} representation of some Java field. * * <li>The object referred to by <code>o</code> is an array, and the offset * is an integer of the form <code>B+N*S</code>, where <code>N</code> is * a valid index into the array, and <code>B</code> and <code>S</code> are * the values obtained by {@link #arrayBaseOffset} and {@link * #arrayIndexScale} (respectively) from the array's class. The value * referred to is the <code>N</code><em>th</em> element of the array. * * </ul> * <p> * If one of the above cases is true, the call references a specific Java * variable (field or array element). However, the results are undefined * if that variable is not in fact of the type returned by this method. * <p> * This method refers to a variable by means of two parameters, and so * it provides (in effect) a <em>double-register</em> addressing mode * for Java variables. When the object reference is null, this method * uses its offset as an absolute address. This is similar in operation * to methods such as {@link #getInt(long)}, which provide (in effect) a * <em>single-register</em> addressing mode for non-Java variables. * However, because Java variables may have a different layout in memory * from non-Java variables, programmers should not assume that these * two addressing modes are ever equivalent. Also, programmers should * remember that offsets from the double-register addressing mode cannot * be portably confused with longs used in the single-register addressing * mode. * * @param o Java heap object in which the variable resides, if any, else * null * @param offset indication of where the variable resides in a Java heap * object, if any, else a memory address locating the variable * statically * @return the value fetched from the indicated Java variable * @throws RuntimeException No defined exceptions are thrown, not even * {@link NullPointerException} */ public native int getInt(Object o, long offset);
(注意:sun.misc.Unsafe是Oracle/Sun JDK里的私有实现,不是Java标准库的共有API,请权衡好利害关系后再使用。)
朋友利用Unsafe.getInt()来实现了快速的反射访问字段的功能,写了这么篇日志:
Java 反射调用的一种优化
在文章末尾,他提到直接用Unsafe.getInt()与用普通反射访问字段的性能对比,
aliveTimeID 写道
通过测试发现,效率是普通java.lang.reflect.Field.get(Object)的3倍,当然,性能这个东西,还是自己测试了放心。
于是问题就来了:在我的机器上也是快三倍么?为什么“快了三倍”?是什么跟什么比较快了三倍?
把原本的测试代码拿来:(有细微改动)
import java.io.Serializable; import java.lang.reflect.Field; import sun.misc.Unsafe; /** * @author haitao.yao Dec 14, 2010 */ public class ReflectionCompare { private static final int count = 10000000; /** * @param args */ public static void main(String[] args) { long duration = testIntCommon(); System.out.println("int common test for " + count + " times, duration: " + duration); duration = testUnsafe(); System.out.println("int unsafe test for " + count + " times, duration: " + duration); } private static long testUnsafe() { long start = System.currentTimeMillis(); sun.misc.Unsafe unsafe = getUnsafe(); int temp = count; Field field = getIntField(); long offset = unsafe.objectFieldOffset(field); while (temp-- > 0) { unsafe.getInt(new TestBean(), offset); } return System.currentTimeMillis() - start; } private static long testIntCommon() { long start = System.currentTimeMillis(); int temp = count; getIntField().setAccessible(true); try { while (temp-- > 0) { TestBean bean = new TestBean(); getIntField().get(bean); } return System.currentTimeMillis() - start; } catch (Exception e) { e.printStackTrace(); } return -1; } private static final sun.misc.Unsafe unsafe; static { sun.misc.Unsafe value = null; try { Class<?> clazz = Class.forName("sun.misc.Unsafe"); Field field = clazz.getDeclaredField("theUnsafe"); field.setAccessible(true); value = (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("error to get theUnsafe", e); } unsafe = value; } public static final sun.misc.Unsafe getUnsafe() { return unsafe; } private static final Field intField; private static final Field stringField; static { try { intField = TestBean.class.getDeclaredField("age"); stringField = TestBean.class.getDeclaredField("name"); } catch (Exception e) { e.printStackTrace(); throw new IllegalStateException("failed to init testbean field", e); } } public static final Field getIntField() { return intField; } public static final Field getStringField() { return stringField; } /** * @author haitao.yao * Dec 14, 2010 */ static class TestBean implements Serializable{ /** * */ private static final long serialVersionUID = -5994966479456252766L; private String name; private int age; /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the age */ public int getAge() { return age; } /** * @param age the age to set */ public void setAge(int age) { this.age = age; } } }
在我的测试环境里跑:
$ java -version java version "1.6.0_25" Java(TM) SE Runtime Environment (build 1.6.0_25-b06) Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode) $ java -cp . ReflectionCompare int common test for 10000000 times, duration: 616 int unsafe test for 10000000 times, duration: 10 $ java -cp . ReflectionCompare int common test for 10000000 times, duration: 425 int unsafe test for 10000000 times, duration: 274 $ java -cp . ReflectionCompare int common test for 10000000 times, duration: 502 int unsafe test for 10000000 times, duration: 10 $ java -cp . ReflectionCompare int common test for 10000000 times, duration: 659 int unsafe test for 10000000 times, duration: 9 $ java -cp . ReflectionCompare int common test for 10000000 times, duration: 646 int unsafe test for 10000000 times, duration: 10
这时间看起来不像是“三倍”,而更像是哪里出岔子了
那么在揭开谜底前,我们来从侧面看看能不能有啥发现。
我们已经知道上面对比的双方是Unsafe.getInt()和Field.get()。Unsafe.getInt()是个native方法,就算是到头了。
实际上Unsafe.getInt()方法在HotSpot VM里是个intrinsic方法,有特别的优化处理。欲知详情可以参考HotSpot server compiler的LibraryCallKit::inline_unsafe_access()函数,它可以将原本的Unsafe.getInt()方法调用内联到调用点上,彻底削除JNI函数调用开销。
但想必这点优化没啥影响,大家是公平的?后面再继续说。
在Oracle/Sun JDK6里,Field.get()又是怎样实现的呢?
跟随Field、ReflectionFactory、UnsafeFieldAccessorFactory、UnsafeFieldAccessorImpl、UnsafeIntegerFieldAccessorImpl这些类的相关方法的实现,可以发现当我们要通过反射获取一个int类型的字段时,最终在UnsafeIntegerFieldAccessorImpl也是通过Unsafe.getInt()来干活的:
public int getInt(Object obj) throws IllegalArgumentException { ensureObj(obj); return unsafe.getInt(obj, fieldOffset); }
也就是说,Oracle/Sun JDK6虽然用动态生成代码的方式来实现反射调用方法,但在反射访问字段上却选择了不动态生成代码而直接使用Unsafe这样的native后门。
那么,自己直接使用Unsafe.getInt()比Field.get()占优的地方就在于前者很直接而后者经过了若干层间接/包装,所以前者比后者快。这足以上面看到的测试时间的差异么?
现在让我们来看看事情的真相。
换用Oracle JDK 6 update 25 build 03 fastdebug版,通过-XX:+PrintOptoAssembly来观察JIT编译器生成出来的代码的样子:
$ ~/jdk/6u25b03_x64_debug/fastdebug/bin/java -cp . -XX:+PrintOptoAssembly ReflectionCompare
可以看到测试里的ReflectionCompare.testUnsafe()方法中的主要计时循环生成为这样的代码:
060 B6: # B6 B7 <- B5 B6 Loop: B6-B6 inner stride: not constant Freq: 999998 060 060 060 decl RBP # int 062 cmpl RBP, #-1 065 jg,s B6 # loop end P=1.000000 C=12203.000000
换回等价的Java代码来表达,也就是:
while (temp-- > 0) { }
但原本测试代码里写的是:
while (temp-- > 0) { unsafe.getInt(new TestBean(), offset); }
而这正是本篇日志的标题所说的:别测空循环。
连被测的对象都被优化掉了的话,这种microbenchmark就彻底没意义了。
前面已经提到,Unsafe.getInt()是HotSpot VM的一个intrinsic方法,因而它的所有特性都是VM清楚了解的,包括有没有副作用、是否能安全的内联、内联后是否可以进一步优化直至完全削除,等。
所以说不要小看了某个native方法的优化对microbenchmark的影响。
用同样办法观察原本测试里的ReflectionCompare.testIntCommon()方法,能看到整个调用过程,包括unsafe.getInt()被内联后的逻辑,都还完好的存在于JIT编译生成的代码中。
在这种条件下比较两边的性能,自然是毫无参考价值可言。
当然,我们还是能得到一些收获。
这个测试里,两边最终干活的都是Unsafe.getInt(),但直接调就优化掉了,而通过Field.get()来调就没被优化掉。
这正好体现了通用型API的多层繁复的封装带来的问题——过多的封装引来了过多间接层,间接层多了之后编译器要优化就会很吃力。
想像一下,如果有人发现直接调用Unsafe.getInt()方法字段的速度很快,然后好心的想把它封装成通用的反射库提供给大家用,很可能发生的状况就是他又把间接层堆积了起来,结果最终用户用的时候就跟直接用反射差不多。
话说回来,一般程序员写Java程序的时候也无需为这种问题纠结就是了。这篇故事看了笑笑就好
发表评论
-
The Prehistory of Java, HotSpot and Train
2014-06-02 08:18 0http://cs.gmu.edu/cne/itcore/vi ... -
MSJVM and Sun 1.0.x/1.1.x
2014-05-20 18:50 0当年的survey paper: http://www.sym ... -
Sun JDK1.4.2_28有TieredCompilation
2014-05-12 08:48 0原来以前Sun的JDK 1.4.2 update 28就已经有 ... -
IBM JVM notes (2014 ver)
2014-05-11 07:16 0Sovereign JIT http://publib.bou ... -
class data sharing by Apple
2014-03-28 05:17 0class data sharing is implement ... -
Java 8与静态工具类
2014-03-19 08:43 16144以前要在Java里实现所谓“静态工具类”(static uti ... -
Java 8的default method与method resolution
2014-03-19 02:23 10338先看看下面这个代码例子, interface IFoo { ... -
HotSpot Server VM与Server Class Machine
2014-02-18 13:21 0HotSpot VM历来有Client VM与Server V ... -
Java 8的lambda表达式在OpenJDK8中的实现
2014-02-04 12:08 0三月份JDK8就要发布首发了,现在JDK8 release c ... -
GC stack map与deopt stack map的异同
2014-01-08 09:56 0两者之间不并存在包含关系。它们有交集,但也各自有特别的地方。 ... -
HotSpot Server Compiler与data-flow analysis
2014-01-07 17:41 0http://en.wikipedia.org/wiki/Da ... -
基于LLVM实现VM的JIT的一些痛点
2014-01-07 17:25 0同事Philip Reames Sanjoy Das http ... -
tailcall notes
2013-12-27 07:42 0http://blogs.msdn.com/b/clrcode ... -
《自制编程语言》的一些笔记
2013-11-24 00:20 0http://kmaebashi.com/programmer ... -
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22259(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局 (0): 拿在手上的是什么
2013-11-04 18:22 21373(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
对C语义的for循环的基本代码生成模式
2013-10-19 23:12 21745之前有同学在做龙书(第二版)题目,做到8.4的练习,跟我对答案 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ...
相关推荐
《检查处理kettle数据流中的空行》示例附件代码 ,是学习kettle 的最佳教程示例,可以在blog中看到本教程。
使用人工神经网络预测联合循环发电厂的电能输出(EP)。 特征包括每小时平均环境变量温度(T),环境压力(AP),相对湿度(RH)和排气真空(V),以预测工厂的每小时净电能输出(EP)。特征包括每小时平均周围环境...
其一是另设一个布尔变量以匹别队列的空和满; 其二是少用一个元素的空间,约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满(注意:rear所指的单元始终为空); 其三是使用一个计数器...
(1)do-while语句圆括号后用分号结束,while语句圆括号后不能有分号,否则产生逻辑错误导致循环体为空,无法修改循环条件,造成无限循环。 (2)while语句循环体为1条语句时可省略{},但好的习惯是不缺省{}。 ...
该代码可在VC6.0平台直接编译运行,经...用数组实现了循环队列的操作,包括入队,出队,队列是否为空,队列是否为满,以及队列的遍历输出功能,各个子函数有详细的说明……希望对正在学习数据结构的同志有所帮组……
静态方法通过程序静态特性的分析,找出欠缺和可疑之处,例如不匹配的参数、不适当的循环嵌套和分支嵌套、不允许的递归、未使用过的变量、空指针的引用和可疑的计算等。静态测试结果可用于进一步的查错,并为测试用例...
深空通信中一种循环能量谱的信号检测方案
针对有限反馈多载波分层空时方案存在反馈链路开销量减少引起性能缺失的问题,利用矩阵线性变换特性,提出了一种基于码本扰动的有限反馈多载波分层空时预编码检测方法。该方法利用多载波各子载波信道频域之间的相关性...
3.ringbuffer为空时,读取时候等待。直到有数据写入,再继续读取。 4.当写入数据的长度大于ringbuffer的可写入长度时,多余的数据将会丢弃。所以写入数据前,先判断ringbuffer的可写入长度。另外程序包含示例。 支持...
3.ringbuffer为空时,读取时候等待。直到有数据写入,再继续读取。 4.当写入数据的长度大于ringbuffer的可写入长度时,多余的数据将会丢弃。所以写入数据前,先判断ringbuffer的可写入长度。 另外程序包含示例。 ...
我们在ABJ理论中找到了分区函数与1/2 BPS Wilson循环的真空期望值(VEV)之间的新精确关系,这使我们能够根据分区的已知结果预测1/2 BPS Wilson循环的大N展开 功能。 这些关系被解释为开-闭对偶,其中由于回响而通过...
以循环链表表示队列,只设置一个尾指针,指向队尾结点,不设置头指针。 要求具有下列接口,并写出主程序测试各个接口: (1)创建空队列 (2)入队 (3)出队 (4)队列是否为空 (5)遍历队列 写一算法实现从循环...
利用上下箭头控制数字的自动循环,如,当最多数字为253时,单击向上箭头,数目自动变为1;反之亦适用; 直接输入超边界值,系统应该提示重新输入; 输入默认值,空白。如,“插入”数目为默认值,点击“确定”;或,...
静态方法通过程序静态特性的分析,找出欠缺和可疑之处,例如不匹配的参数、不适当的循环嵌套和分支嵌套、不允许的递归、未使用过的变量、空指针的引用和可疑的计算等。静态测试结果可用于进一步的查错,并为测试用例...
Java jxls 替换小列子,主要包含了所需要的所有jar , 下载下来就可以运行
废气再循环控制系统由电控单元、三通电磁阀、废气再循环阀、废气调整阀及废气干道和真空管道组成。系统中的任一部件损坏都会造成系统工作不正常,导致怠速运转不稳或增加排放污染。 1。排放控制系统主要故障现象...
运行>启动Lowen测试.bat 直接启动测试或者命令行lowen -s(该命令会自动新建空的mr目录) 如果没有mr文件夹,先新建mr文件夹,然后按照mr_samples目录下面的脚本开始编写测试脚本 例子4 ...
基于汽车废气再循环系统,实验室模拟真空环境,使用LabVIEW开发平台,设计出一套对真空电磁阀的各项性能指标进行测试的系统。测试系统已成功投入应用,在测试中实现对压力、流量的实时数据采集、处理、显示和存储。...
一个测试,循环遍历allFeeds对象中的每个提要,并确保已定义URL,并且该URL不为空。 一个测试,循环遍历allFeeds对象中的每个提要,并确保已定义名称,并且该名称不为空。 确保菜单元素默认为隐藏的测试。 单击...
/*如果初始化失败,那么capture为空指针,程序停止,否则进入捕获循环*/ if( capture ) { for(;;) { IplImage* frame = cvQueryFrame( capture ); IplImage* img = NULL; CvSeq* faces; if( !frame ) ...