`
RednaxelaFX
  • 浏览: 3047882 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

举例的时候要小心

阅读更多
下午翻了一下老资料,又读了一遍JVM Language Summit 2008上John Pampuch做的一个演示稿:VM Optimizations for Language Designers。读到第18页的时候都还没觉得有什么问题,但第20-21页演示的常量折叠在JDK6的HotSpot里明明没有实现……数组的创建也在sum()方法里的时候,开上EA,才能看到演示稿中所描述的效果;private static final int[]的话HotSpot只是展开了循环却没有对数组内容做常量折叠:因为光看sum()方法无法知道数组元素有没有变过。所以说读这些演示稿的时候要很小心,把里面提到的内容都自己验证一次。

把虚方法内联、循环展开、逃逸分析+标量替换、常量折叠等优化的演示放在一起做个例子。上述演示稿中的例子大致跟下面的FooA版bar()一样。

public class TestC2ConstantFolding {
    private static void driver() {
        IFoo[] array = new IFoo[] {
            new FooA(), new FooB(), new FooC(), new FooD()
        };
        for (int i = 0; i < 100000; i++) {
            array[i % array.length].bar(); // megamorphic callsite to prevent inlining
        }
    }
    
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 1000; i++) {
            driver();
        }
        System.in.read();
    }
}

interface IFoo {
    int bar();
}

class FooA implements IFoo {
    private static final int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    public int number(int i) {
        return numbers[i];
    }
    
    public int bar() {
        int sum = 0;
        for (int i = 0; i < numbers.length; i++) {
            sum += number(i);
        }
        return sum;
    }
}

class FooB implements IFoo {
    private static final int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    public int number(int i) {
        return numbers[i];
    }
    
    public int bar() {
        int sum = 0;
        int i = 0;
        
        sum += number(i++);
        sum += number(i++);
        sum += number(i++);
        sum += number(i++);
        sum += number(i++);
        sum += number(i++);
        sum += number(i++);
        sum += number(i++);
        sum += number(i++);
        sum += number(i++);
        
        return sum;
    }
}

class FooC implements IFoo {    
    public int bar() {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        int sum = 0;
        for (int i = 0; i < numbers.length; i++) {
            sum += numbers[i];
        }
        return sum;
    }
}

class FooD implements IFoo {
    public int bar() {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        int sum = 0;
        int i = 0;
        
        sum += numbers[i++];
        sum += numbers[i++];
        sum += numbers[i++];
        sum += numbers[i++];
        sum += numbers[i++];
        sum += numbers[i++];
        sum += numbers[i++];
        sum += numbers[i++];
        sum += numbers[i++];
        sum += numbers[i++];
        
        return sum;
    }
}


在Sun的JDK 6u17和6u21的server模式上跑,只有FooD版的bar()方法最终生成的代码与开头说的演示稿的示例一样,是:
public class FooD implements IFoo {
    public int bar() {
        // safepoint inserted here
        
        return 55; // constant-folded
    }
}


FooC的版本虽然有常量折叠,但数组的创建却没削除掉,会试图在thread-local storage上为这个数组分配空间:
public class FooC implements IFoo {
    public int bar() {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        
        // safepoint inserted here
        
        return 55; // constant-folded
    }
}

这里到底跟FooD版差在什么地方我还没弄清楚……得用IGV仔细看看才行了。

FooA与FooB版本的基本上一样,都如预期般把number()方法内联了,并且做了循环展开+冗余数组边界检查削除,但没有对数组内容求和这个操作进行常量折叠——因为numbers是静态变量,虽然声明为final使得该变量只可能指向一个固定的数组,但数组的内容仍然是可变的,至少要在类一级做分析才可以确定没有别的方法修改过该数组的元素,而HotSpot的代码分析都是在方法一级做的,就无能为力了。生成的代码基本上等价于:
class FooA implements IFoo {
    private static final int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    public int bar() {
        int sum = 0;
        
        sum += numbers[0];
        sum += numbers[1];
        sum += numbers[2];
        sum += numbers[3];
        sum += numbers[4];
        sum += numbers[5];
        sum += numbers[6];
        sum += numbers[7];
        sum += numbers[8];
        sum += numbers[9];
        
        
        // safepoint inserted here
        
        return sum;
    }
}

// FooB is the same

注意在实际生成的代码里数组越界检查的开销已经完全削除了,这里用Java代码表现不出来。

于是那个演示稿不只是“over simplification”,而是演示了一个尚未实现的优化(虽说那优化不是不可能做到)。

======================================================

出于好奇心,顺带拿JRockit R28来对比测试了一下。确认了上面的代码通过在一个callsite调用同一接口方法的多个实现能有效阻止JRockit把我要测的方法内联到“热身”代码中。
以前一直没拿JRockit来玩过,只是读过些资料。实际一玩觉得在JIT上与预期的表现有落差。上面4个版本的代码在JRockit的全优化模式下编译出来形如:
class FooA implements IFoo {
    private static final int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    public int bar() {
        // safepoint inserted here
        
        int sum = 0;
        for (int i = 0; i < 10; i++) { // numbers.length is constant-propagated
            // safepoint inserted here
            
            sum += numbers[i]; // array bounds check not eliminated
        }
        return sum;
    }
}

class FooB implements IFoo {
    private static final int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    public int bar() {
        // safepoint inserted here
        
        int sum = 0;
        
        sum += numbers[0]; // array bounds check not eliminated
        sum += numbers[1]; // array bounds check not eliminated
        sum += numbers[2]; // array bounds check not eliminated
        sum += numbers[3]; // array bounds check not eliminated
        sum += numbers[4]; // array bounds check not eliminated
        sum += numbers[5]; // array bounds check not eliminated
        sum += numbers[6]; // array bounds check not eliminated
        sum += numbers[7]; // array bounds check not eliminated
        sum += numbers[8]; // array bounds check not eliminated
        sum += numbers[9]; // array bounds check not eliminated
        
        return sum;
    }
}

class FooC implements IFoo {    
    public int bar() {
        // safepoint inserted here
        
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        int sum = 0;
        for (int i = 0; i < 10; i++) { // numbers.length is constant-propagated
            // safepoint inserted here
            
            sum += numbers[i]; // array bounds check not eliminated
        }
        return sum;
    }
}

class FooD implements IFoo {
    public int bar() {
        // safepoint inserted here
        
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        return 55; // constant-folded
    }
}

虚方法内联是做了,常量传播也有做,但循环展开、冗余数组越界检查的削除却不尽人意。JRockit R28为这4个版本的bar()方法生成的代码质量都比HotSpot生成的差一些。或许是这里的预热方式还不够适合JRockit,但我试过强制指定优化模式还是得到了一样的结果。要摸清这位大小姐的脾气看来还得花不少时间……

==============================================================

P.S. Sovereign和J9里有field privatization,不知道能不能实现这例子里的优化呢。
分享到:
评论
2 楼 RednaxelaFX 2010-12-05  
taolei0628 写道
有个问题请指教一下,我E文不太好,看原始资料比较吃力。
关于虚方法内联,是基于怎样的规则的?
final/private/static方法做inline优化比较容易理解,但虚方法怎么判断它可以象非虚方法一样可以内联,不用担心它被子类重写?

请下载我之前做分享用的演示稿来参考,里面有提到其中一种办法,类层次分析(Class Hierarchy Analysis,CHA)。简单来说就是动态编译器可以做非常激进的假设,那么就可以假设“当前状况”就是以后程序运行的完整情况,在这些假设变得不成立的时候退回到不那么优化但比较安全的代码上。如果在一个类继承层次里,发现某个方法其实只有一个版本的话,那么就算那个是虚方法,内联进来也会是安全的——而如果有新的类加载进来破坏了前面的假设(那个方法变得不只有一个版本了),则可以抛弃掉这种激进优化后的代码而退回到例如说解释执行。
1 楼 taolei0628 2010-12-05  
有个问题请指教一下,我E文不太好,看原始资料比较吃力。
关于虚方法内联,是基于怎样的规则的?
final/private/static方法做inline优化比较容易理解,但虚方法怎么判断它可以象非虚方法一样可以内联,不用担心它被子类重写?

相关推荐

    Excel 的使用技巧、常用快捷方式和指令举例.txt

    以下是一些 Excel 的使用技巧、常用快捷方式和指令举例: 一、快捷方式 Ctrl + C / Ctrl + V / Ctrl + X:分别为复制、粘贴、剪切。可以快速复制单元格内容、格式等,或剪切单元格并粘贴到其他位置。 Ctrl + Z / ...

    k均值聚类分析举例.pdf

    K均值聚类分析举例 K均值聚类分析是一种常用的无监督学习算法,用于发现数据中的聚类结构。该算法通过将数据点分配到K个簇中,以最小化簇内平方误差为目标。K均值聚类分析广泛应用于数据挖掘、机器学习、生物信息学...

    举例介绍Python中的25个隐藏特性

    使用可变类型(如列表)作为函数默认参数要格外小心,因为默认参数只在函数定义时被初始化一次。 ```python def foo(x=[]): x.append(1) print(x) foo() # 输出[1] foo() # 输出[1, 1] ``` 为了避免这个问题,...

    秋五年级品社上册小心电老虎浙教解读PPT学习教案.pptx

    在课程的实践环节,学生被鼓励进行小组讨论,找出更多不安全的用电行为,并通过制作安全用电提示卡来巩固所学知识,例如提醒家长在更换灯泡时要先关闭电源,避免同时插过多插头等。 总的来说,这份《小心电老虎——...

    C语言和其他编程语言相比有什么优点,并举例说明C语言编写代码的过程.docx

    然而,C语言也有其缺点,如对内存管理的要求较高,程序员需要手动分配和释放内存,如果不小心可能会导致内存泄漏或指针错误。此外,C语言缺乏一些现代编程语言中的高级特性,如垃圾回收和自动类型检查。 通过学习和...

    EPSON R220/R230清零软件

    普生 R230、220 清零软件 epson r220.230 清零软件,使用时请选择相应的打印机, 打印机有两个红灯交替闪,进纸灯和墨水灯同时在闪!就说明应该对打印机清零...清零时一定选择 R230) 使用了废墨仓计数归零*用它一定要小心

    数据库中各种SQL语句的应用

    与`UPDATE`语句一样,如果没有指定`WHERE`子句,则会删除表中的所有记录,因此使用时需格外小心。 #### 综合示例 为了更好地理解这些基本的SQL语句如何协同工作,我们可以构建一个简单的示例场景。假设我们有一个...

    SQL常用查询语句大全

    不加WHERE子句将删除整个表的所有记录,所以要格外小心。 除了这些基础操作,SQL还提供了更复杂的查询功能: - JOIN操作:用于合并来自两个或更多表的数据。例如,INNER JOIN返回两个表中匹配的记录,LEFT JOIN...

    程序员面试如何介绍自己优缺点..pdf,这是一份不错的文件

    举例:对自己的能力总是不满足,会去主动尝试学习一些新的东西。高中的时候英语成绩突出,经常能考到140 分以上,但是我并没有满足,利用假期考了雅思,成绩也还不错,虽然不出国,但是当做是对自己的一种挑战,很有...

    JS 新增Cookie 取cookie值 删除cookie 举例详解

    删除Cookie的时候,我们要先获取到目标Cookie的值,然后设置其过期时间到一个过去的时间点,如下所示: ```javascript function delCookie(name) { var exp = new Date(); exp.setTime(exp.getTime() - 1); // ...

    一些SQLServer存储过程参数及举例

    这些扩展存储过程提供了对操作系统级别的交互,但使用时需要小心,因为它们可能影响到系统安全性和稳定性。 需要注意的是,大多数这些存储过程要求执行者属于`sysadmin`固定服务器角色,这意味着只有具有足够权限的...

    字符串函数string.h应用举例.-综合文档

    `strncpy(dest, src, n)`复制`n`个字符,但不会自动添加结束符`\0`,所以使用时需小心。例如: ```c char str1[20] = "Hello, World!"; char str2[10]; strncpy(str2, str1, 10); str2[9] = '\0'; // 添加结束符 ...

    举例初步讲解Ruby中的正则表达式

    首先,由于正则表达式有其特殊的语法,所以在编写模式时必须小心,确保使用正确的字符和符号。其次,使用正则表达式进行复杂匹配时可能会降低程序的性能,特别是在处理大型文本或者非常复杂的模式时。因此,在使用...

    招商银行产品介绍技巧.pptx

    例如,“我们这份保险一天只要2块5毛钱,保额却高达75万元,不但火灾、地震等自然灾害在承保范围之内,而且连日常生活中的扭伤、磕伤、平时不小心被刀切伤等意外也在承保范围的。” 本资源摘要信息介绍了招商银行...

    Oracle表连接的具体讲解

    "Oracle表连接详解" 在 Oracle 中,表连接是指将两个或多个表中的数据结合起来,以满足查询的需求。表连接可以分为三种:内连接、外连接和自连接...然而,使用表连接需要非常小心,否则可能会导致查询结果的不正确性。

    幽默的自我介绍三篇精选.doc

    5. 自我评价:以幽默的方式自评,如“我也是天使,只不过降落的时候不小心脸先着地了”,既展现自信,又不失风趣。 6. 强调优点与改进空间:指出自己的优点(如体育好、学习能力强)和缺点(如上课走神),并展示...

    新版部编版三年级道德与法治(上册)月考试题及答案(全面).pdf

    17. 关心他人需求:指出“在关心和帮助他人的时候,要用心了解他人的需要”,培养学生的同理心和助人精神。 18. 公共安全与规则意识:题目强调公共安全需要大家共同维护,以及遵守安全规定的重要性。 19. 不同年龄...

    finaldata3.0汉化版

    这里可以使用文件恢复软件Finaldata(此软件可以通过网络下载获取)举例说明恢复文件的方法。 (1)打开Finaldata应用程序,单击【文件】→【打开】,弹出【选择驱动器】对话框。 (2)在【逻辑驱动器】中选择文件...

    FinalData3.0汉化版

    这里可以使用文件恢复软件Finaldata(此软件可以通过网络下载获取)举例说明恢复文件的方法。 (1)打开Finaldata应用程序,单击【文件】→【打开】,弹出【选择驱动器】对话框。 (2)在【逻辑驱动器】中选择文件是...

    freewrap651

    所以在添加库文件时要十分小心,要保证在脚本中调用的文件为source \myfile\lib.tcl而不是source lib.tcl。 -f 可以罗列需要打包的文件路径名到一个txt文件当中,运行命令后自动添加,避免命令过长。比如有3个...

Global site tag (gtag.js) - Google Analytics