- 浏览: 3052794 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (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 16293以前要在Java里实现所谓“静态工具类”(static uti ... -
Java 8的default method与method resolution
2014-03-19 02:23 10465先看看下面这个代码例子, 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 22408(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局 (0): 拿在手上的是什么
2013-11-04 18:22 21508(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 21884之前有同学在做龙书(第二版)题目,做到8.4的练习,跟我对答案 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ...
相关推荐
最后,将余数拼到信息码左移后空出的位置,得到完整的 CRC 码。 例如,数据码 M=101001,生成码 P=1101,则信息码多项式为 K(x)=x5+x3+1,生成多项式为 G(X)=X8+X6+X3。模 2 除法运算结果为 R=001,则 CRC 码为 ...
我们可以设置规则,例如检查行是否为空,即所有字段都没有值。如果满足条件,该行将被排除,不传递到下一个步骤。 4. **条件设置**:在"Filter Rows"步骤中,我们需要配置条件表达式。对于空行,我们可以检查每列的...
循环码是一种特殊的纠错编码技术,广泛应用于数据通信、存储系统和深空探测等领域。它的核心原理是通过在原始数据中添加冗余位,使得整个编码序列具有循环特性,即任意长度的子序列首尾相连都能形成一个合法的编码。...
采购与付款业务循环符合性测试程序表 本文档介绍了采购与付款业务循环符合性测试程序表的知识点,涵盖了采购、验收、发票传递、付款和会计处理等方面。 一、采购部分 * Auditing the segregation of duties ...
减少Linux内核空循环是优化系统能耗的关键策略之一,尤其对于依赖电池电源的便携式设备如笔记本电脑和服务器来说,降低能耗不仅意味着更长的电池寿命,还能够减少运行成本。Linux内核的"tickless"技术正是针对这一...
循环顺序队列是对普通顺序队列的改进,解决了顺序队列在满时无法继续插入元素,空时无法继续删除元素的问题。 循环顺序队列是一种线性结构,它在内存中连续分配空间,并通过指针来模拟队列的“首”和“尾”。在循环...
12. **应用场景**:循环码广泛应用于移动通信、卫星通信、深空探测、数据存储系统以及计算机网络中,以确保数据传输的准确性。 13. **优化与改进**:随着技术的发展,人们不断探索新的编码理论和方法,如低密度奇偶...
- **循环控制**:你可以在循环体内修改控制循环的布尔值,实现循环的提前退出或无限循环。 - **结束条件**:需要预先设定一个或多个条件,当这些条件满足时,改变循环头的布尔值,使循环停止。 在提供的例子“while...
循环队列是一种线性数据结构,它在计算机科学中被广泛应用于实现各种算法,如缓存、消息队列等。在C#编程语言中,我们可以使用内置的数据结构或自定义结构来实现循环队列。本篇文章将深入探讨C#中的循环队列,包括其...
- 在循环边界条件下测试循环体的正确性。 - 检查内部数据结构的有效性。 ##### 3. 测试用例设计方法 - **路径覆盖**:确保软件的所有可能路径都被测试到。 - **分支覆盖**:测试所有逻辑判断的真/假两种结果。 - **...
为了符合环保法规,现代汽车配备了碳罐控制系统,该系统捕获并再循环这些燃油蒸汽,以减少排放。在这个过程中,涉及到的电气系统必须维持稳定的电压,确保系统的正常运行。 负峰值电压检测有助于预测可能出现的电压...
在IT行业中,流程循环是软件开发过程中的一个重要环节,它涉及到项目管理、质量控制和持续改进等多个方面。QTC(Quality Assurance and Testing Cycle)是一种常见的质量保证和测试流程,旨在确保产品的质量和性能...
循环链表的测试主要涉及验证上述基本操作(插入、删除、创建、输出)的正确性,以及检查链表在各种边界条件下的行为,如空链表、只有一个元素的链表等情况。此外,还需要测试链表在高负载或异常情况下的稳定性和性能...
在常规队列满或空的情况下,循环队列能避免这些问题,因为它可以在队尾出队后立即在原位置入队,从而提高效率。在串口通信中,循环队列常用于缓冲数据,确保数据的连续性和一致性,尤其是在数据量大或者接收速率快的...
循环队列是一种常见的线性数据结构,它具有先进先出(FIFO)的特性,即最早进入队列的元素最先离开。这个特性使得循环队列在处理一系列有序任务或者缓存管理等方面有着广泛的应用。本文将详细探讨Android中如何实现...
循环链表是一种特殊类型的链表,它在最后一个元素之后连接回第一个元素,形成一个闭合的环状结构。这种数据结构在处理具有周期性或循环性质的问题时特别有用。在这个项目中,我们有两个源文件:`CirLL.cpp` 和 `main...
`initialize()`函数用于创建空链表,设置头指针为nullptr。 `insert()`函数接收一个值,创建新节点并将其插入到链表的适当位置。插入操作可能发生在链表头部、尾部或中间,具体取决于插入规则。 `remove()`函数...
循环码在许多实际应用中都有所体现,如CD-ROM、DVD、卫星通信、深空探测和无线网络等。在这些领域,循环码能提高数据传输的可靠性,确保数据在经过噪声通道后仍能准确恢复。 总结,本项目提供了一个用C语言实现的...
循环双链表是一种数据结构,它在单链表的基础上增加了前后指针,使得链表的首尾相连,形成一个环状。在Java编程中,我们可以使用面向对象的设计思想来实现循环双链表。通常,这样的数据结构会包含三个主要部分:一个...