C1 RCE对%的处理

HotSpot VM的C1有个RCE(Range Check Elimination,范围检查消除)优化,所谓范围检查消除,就是为了正确的抛出数组越界异常,虚拟机需要在数组访问的一些地方插入隐式的检查,但是这些检查会降低性能,比如在循环中每次循环都得检查一次,所以HotSpot VM会想办法在可能的地方消除这些检查。我在看C1 RCE的时候发现目前它对求余符号的支持较为薄弱,它只能处理形如下面的代码:

arr[x%arr.length] // 只有除数是x.length的时候,才能应用RCE优化

如果余数是整数常量,它就不能工作了:

arr[x%3]
for(int i=0;i<10;i++){
arr[x%10]
}

实际上,根据JLS的定义,我们知道如果除数为整数常量(且等于零,因为0作为除数会抛出运行时异常),是可以推导出结果的上下界的(也取决于被除数的正负),规则如下:

x % -y ==> [0, y - 1]
x % y ==> [0, y - 1]
-x % y ==> [-y + 1, 0]
-x % -y ==> [-y + 1, 0]

于是,我给JDK发了个patch,这个问题算是解决了。但是Nils提到,C2是否有相同的优化呢?后面Tobias帮忙确认了一下C2没有,我再后来也进一步确认了,所以下一步是调研C2是否能应用同样的优化。

调研为C2应用同样的优化

本来以为是比较trivial的事情,为求余节点的类型系统加点代码,推导一下上下界即可,实际上我也这么做的,但是最后发现这样没有消除上下界:

Node* Parse::array_addressing(BasicType type, int vals, const Type*& elemtype) {
Node *idx = peek(0+vals); // Get from stack without popping
Node *ary = peek(1+vals); // in case of exception // Null check the array base, with correct stack contents
ary = null_check(ary, T_ARRAY);
// Compile-time detect of null-exception?
if (stopped()) return top(); const TypeAryPtr* arytype = _gvn.type(ary)->is_aryptr();
const TypeInt* sizetype = arytype->size();
elemtype = arytype->elem(); if (UseUniqueSubclasses) {
...
} // Check for big class initializers with all constant offsets
// feeding into a known-size array.
const TypeInt* idxtype = _gvn.type(idx)->is_int();
// See if the highest idx value is less than the lowest array bound,
// and if the idx value cannot be negative:
bool need_range_check = true;
if (idxtype->_hi < sizetype->_lo && idxtype->_lo >= 0) {
need_range_check = false;
if (C->log() != NULL) C->log()->elem("observe that='!need_range_check'");
} ciKlass * arytype_klass = arytype->klass();
if ((arytype_klass != NULL) && (!arytype_klass->is_loaded())) {
// Only fails for some -Xcomp runs
// The class is unloaded. We have to run this bytecode in the interpreter.
uncommon_trap(Deoptimization::Reason_unloaded,
Deoptimization::Action_reinterpret,
arytype->klass(), "!loaded array");
return top();
} // Do the range check
if (GenerateRangeChecks && need_range_check) {
... // need_range_check仍然为true
}
}

need_range_check仍然为true,调试后发现推导上下界根本没有执行,因为C2创建完求余节点后,会执行一个IGVN的过程,即迭代的应用多种优化,其中就包括理想化,C2理想化是指应用很多局部小优化的过程,在这个例子中就是特殊处理形如x%2^n,x%2^n-1x%1的情况,如果除数是整数常量,它还会使用一个来自https://book.douban.com/subject/1784887/书里面的算法,即Division by Invariant Integers using Multiplication(by Granlund and Montgomery),搜了一下知乎有类似的文章,想要了解细节可以读读https://zhuanlan.zhihu.com/p/151038723。知道了原因,于是我改了下代码,禁止了求余节点的理想化,心想这总可以了吧。

还是不行

尽管我已经禁止了对求余符号的理想化优化,但是范围检查还是生成了。。。我又继续看代码,发现除了理想化的这个优化之外,C2在IR(中间表示)构造的过程中又 又 又 又 又对求余运算做了个优化!如果除数是正整数常量,且是2^n,那么C2会对它进行变形,IR如图所示:

左边的IR是 IR构造的时候C2做的优化后的效果,右边是理想化优化后的效果。实际上它们做的事情本身是比较重复的,而且经过测试发现,理想化优化的算法要好于IR构造过程中的优化,所以我又提了个patch,尝试解决这个问题(不过还在review中)。

结语

我认为为求余节点推导上下界也是有意义的,如果以后有其他优化会变形为求余运算,那么它们可以应用这个推导,同时,为求余做统一完善的类型推导这件事本身也是正确的,所以我又提了个patch。尽管如此,可以看到最终我只消除了C1 arr[x%4]的范围检查,还是没能消除C2 arr[x%4]的范围检查,是不是以后可以说C1有的地方做的比C2好了(狗头hh。

Java求余%引发的一连串故事的更多相关文章

  1. 年年有余之java求余的技巧集合

    背景 传说里玉皇大帝派龙王马上降雨到共光一带,龙王接到玉皇大帝命令,立马从海上调水,跑去共光施云布雨,但粗心又着急的龙王不小心把海里的鲸鱼随着雨水一起降落在了共光,龙王怕玉皇大帝责怪,灵机一动便声称他 ...

  2. java中的取整(/)和求余(%)

    1.取整运算符取整从字面意思理解就是被除数到底包含几个除数,也就是能被整除多少次,那么它有哪些需要注意的地方呢?先看下面的两端代码: int a = 10; int b = 3; double c= ...

  3. java中求余%与取模floorMod的区别

    初学java的时候接触的%这个符号 百分号? 求余? 取模? 我只知道不是百分号,好像是求余,听别人那叫求模运算符,跟求余一样,于是我便信了. 思考之后开始迷糊,然后经过多次考证得到以下结论. 首先, ...

  4. java 整除(/) 求余(%) 运算

    1. java 整除(/)  求余(%)  运算 1.求余    System.out.println(11%2);     //顾名思义就是11除2的余数-->1    System.out. ...

  5. ACM-ICPC 2018 焦作赛区网络预赛 G Give Candies(高精度求余)

    https://nanti.jisuanke.com/t/31716 题意 n颗糖果n个人,按顺序给每个人任意数目(至少一个)糖果,问分配方案有多少. 分析 插板法或者暴力打表后发现答案就为2^(n- ...

  6. BigDecimal求余操作

    BigDecimal求余操作如下: package com.qiu.lin.he; import java.math.BigDecimal; public class CeShi { public s ...

  7. C语言fmod()函数:对浮点数取模(求余)

    头文件:#include <math.h> fmod() 用来对浮点数进行取模(求余),其原型为:    double fmod (double x); 设返回值为 ret,那么 x = ...

  8. 求余VS求模--C语言中表述

    之前看帖子,发现许多时候基本上大家都把求模和求余混为一谈了.但实际上二者的概念是有区别的   1. 求余 在C语言中,求余对应的操作符是%,且a%b求余的最后结果总是与a符号相同,最后的数值为|a|% ...

  9. 【转】C/C++求模求余运算符——2013-08-20

    http://blog.csdn.net/whealker/article/details/6203629 求模运算符(%),或称求余运算符,也就是数学上所谓的除法中的余数,%两侧均应为整数, |小| ...

随机推荐

  1. Spring学习笔记(五):JDBCTemplate+事务管理

    1 概述 Spring为开发者提供了JDBCTemplate,可以简化很多数据库操作相关的代码,本文主要介绍JDBCTemplate的使用以及事务管理功能. 2 JDBC Template 2.1 配 ...

  2. 4. Linux-startx命令

    Linux系统startx命令的功能和使用方法 Linux系统命令startx的功能很简单,就是启动X Window的服务这一项,没有其他的了.其实startx命令启动的是xinit,然后再由xini ...

  3. HashSet、CopyOnWriteArraySet、ConcurrentSkipListSet源码解析(JDK1.8)

    目录 HashSet源码解析 HashSet简单使用的demo HashSet中的变量 HashSet的构造函数 HashSet的add方法 HashSet的iterator方法 HashSet的si ...

  4. Ugly Numbers UVA - 136

    Ugly numbers are numbers whose only prime factors are 2, 3 or 5. The sequence 1, 2, 3, 4, 5, 6, 8, 9 ...

  5. JAVAEE_Servlet_05_ServletConfig接口

    ServletConfig接口 研究javax.servlet.ServletConfig接口 1.javax.servlet.ServletConfig是一个接口 2.Apache Tomcat服务 ...

  6. Day09_41_集合_Set

    Set集合 Set集合 - Set集合的特点是无序不可重复.Set集合类似于一个罐子,程序可以依次把多个对象"丢进"Set集合,而Set集合通常不能记住元素的添加顺序. - Set ...

  7. pod详解

    什么是pod? 官方说明: Pod是Kubernetes应用程序的最基本执行单元-是你创建或部署Kubernetes对象模型中的最小和最简单的单元. Pod表示在集群上运行的进程.Pod封装了应用程序 ...

  8. 2021S软件工程——个人阅读作业2

    2021S软件工程--个人阅读作业2 项目 内容 这个作业属于哪个课程 2021春季软件工程(罗杰 任建) 这个作业的要求在哪里 个人阅读作业#2 我在这个课程的目标是 了解并熟悉软件开发的具体流程, ...

  9. Mysql Char 和 Varchar的区别

    CHAR和VARCHAR都是字符串类型,它们的具体区别为: 长度大小区别: CHAR(M)定义的列的长度为固定的,M取值可以为0-255之间: VARCHAR(M)定义的列的长度为可变长,M取值可以为 ...

  10. Google Hacking的用法

    目录 Google Hacking 基本搜索 高级搜索 Index of inurl Google Hacking Google Hacking 是利用谷歌搜索的强大,来在浩瀚的互联网中搜索到我们需要 ...