`
RednaxelaFX
  • 浏览: 3016610 次
  • 性别: 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优化比较容易理解,但虚方法怎么判断它可以象非虚方法一样可以内联,不用担心它被子类重写?

相关推荐

    EPSON R220/R230清零软件

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

    网络安全与网络道德教学反思.doc

    所以在课堂上,我举例很多,各方各面的 基本都有,也把事情说得特别严重,希望他们能意识到现在网络不但精彩也很危险,稍 微不小心,我们就会吃亏。 网络道德与网络安全,首先是从"全国青少年网络文明公约"说起,...

    finaldata3.0汉化版

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

    FinalData3.0汉化版

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

    freewrap651

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

    如何判断Javascript对象是否存在的简单实例

    Javascript语言的设计不够严谨,很多地方一不小心就会出错。 举例来说,请考虑以下情况。 现在,我们要判断一个全局对象myObj是否存在,如果不存在,就对它进行声明。用自然语言描述的算法如下: if (myObj不存在){...

    JS判断对象是否存在的10种方法总结

    Javascript语言的设计不够严谨,很多地方一不小心就会出错。举例来说,请考虑以下情况。现在,我们要判断一个全局对象myObj是否存在,如果不存在,就对它进行声明。用自然语言描述的算法如下: 代码如下:if (myObj不...

    摩托罗拉C++面试题

    (最好这个项目继承,多态,虚函数都有体现)这个问题大概会占面试时间的一半,并且会问很多问题,一不小心可能会被问住)。 。。。 12。基类的有1个虚函数,子类还需要申明为virtual吗?为什么。 不申明没有关系的...

    ChineseFramework大型WEB应用框架程序

    输出由于采用了pfcOutputStrem 类进行封装, 再也不怕不小心出 现在header之前进行输出了.(没经过本类的不算) 并可在输出前进行可种处理. 如压缩(可手工用自己的算法或ob), 替换等操作. 使用举例: $...

    大数据的财务管理.doc

    "IMA负责研究部 门的副总裁Raef Lawson博士指出,"我们已经注意到有这样的高调抗议活动,反对组织持有数据,甚至某 些时候出售数据.对于财会专业人士来说,引导他们的企业小心避开道德和法律的雷区至 关重要。" ...

    [关于人类与人工智能的作文范文]人工智能与人类未来.docx

    人类与人工智能的作文:人工智能距离消灭人类还有多远 特斯拉汽车公司的创始人之一埃伦 马斯克最近患上了"科技恐惧症",他在社交网络上连发两条耸人言论,警告人类要对人工智能特别小心,因为人工智能在未来很可能比...

    .htaccess

    虽然在服务器上使用.htaccess绝对不太可能给你带来任何麻烦(如果有些东西错了,它只是没效用罢了),但如果你使用Microsoft FrontPage Extensions,就必须特别小心。因为FrontPage Extensions本身使用了.htaccess,...

    第吉尔电子门锁系统(接触式IC卡门锁)

    楼层转换时,房间设置举例:3楼有25间客房,房号从0301至0325,依次设置,操作员应再额外增加一个房间,房号为0326,将此房用鼠标选中将层号”3”楼改为”4”楼,房号 “0326”改为 “0401”号房,在此基础上可依次增加4楼...

    网管教程 从入门到精通软件篇.txt

    放入xp(2000)的光盘,安装时候选R,修复! Windows XP(包括 Windows 2000)的控制台命令是在系统出现一些意外情况下的一种非常有效的诊断和测试以及恢复系统功能的工具。小编的确一直都想把这方面的命令做个总结...

    德力西产品说明.pdf

    CDI9600系列小功率矢量变频器安装、运行、维护和检查之前要认真阅读本说明书。 为了确保您的人身、设备及财产安全,在使用我公司的CDI9600系列小功率矢量变频器之前,请务必仔细阅读本章内容。说明书中有关安全运行...

Global site tag (gtag.js) - Google Analytics