Java求余%引发的一连串故事
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-1
和x%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求余%引发的一连串故事的更多相关文章
- 年年有余之java求余的技巧集合
背景 传说里玉皇大帝派龙王马上降雨到共光一带,龙王接到玉皇大帝命令,立马从海上调水,跑去共光施云布雨,但粗心又着急的龙王不小心把海里的鲸鱼随着雨水一起降落在了共光,龙王怕玉皇大帝责怪,灵机一动便声称他 ...
- java中的取整(/)和求余(%)
1.取整运算符取整从字面意思理解就是被除数到底包含几个除数,也就是能被整除多少次,那么它有哪些需要注意的地方呢?先看下面的两端代码: int a = 10; int b = 3; double c= ...
- java中求余%与取模floorMod的区别
初学java的时候接触的%这个符号 百分号? 求余? 取模? 我只知道不是百分号,好像是求余,听别人那叫求模运算符,跟求余一样,于是我便信了. 思考之后开始迷糊,然后经过多次考证得到以下结论. 首先, ...
- java 整除(/) 求余(%) 运算
1. java 整除(/) 求余(%) 运算 1.求余 System.out.println(11%2); //顾名思义就是11除2的余数-->1 System.out. ...
- ACM-ICPC 2018 焦作赛区网络预赛 G Give Candies(高精度求余)
https://nanti.jisuanke.com/t/31716 题意 n颗糖果n个人,按顺序给每个人任意数目(至少一个)糖果,问分配方案有多少. 分析 插板法或者暴力打表后发现答案就为2^(n- ...
- BigDecimal求余操作
BigDecimal求余操作如下: package com.qiu.lin.he; import java.math.BigDecimal; public class CeShi { public s ...
- C语言fmod()函数:对浮点数取模(求余)
头文件:#include <math.h> fmod() 用来对浮点数进行取模(求余),其原型为: double fmod (double x); 设返回值为 ret,那么 x = ...
- 求余VS求模--C语言中表述
之前看帖子,发现许多时候基本上大家都把求模和求余混为一谈了.但实际上二者的概念是有区别的 1. 求余 在C语言中,求余对应的操作符是%,且a%b求余的最后结果总是与a符号相同,最后的数值为|a|% ...
- 【转】C/C++求模求余运算符——2013-08-20
http://blog.csdn.net/whealker/article/details/6203629 求模运算符(%),或称求余运算符,也就是数学上所谓的除法中的余数,%两侧均应为整数, |小| ...
随机推荐
- ACCESS常见错误场景
ACCESS常见错误场景 今天用access时发现好多报错的地方,emmm,比MySQL麻烦好多,有些甚至还要自己去配置环境 不吐槽了,进入正题: 报错场景一:您尝试执行不包含指定聚合函数的查询 第一 ...
- JMeter循环读取CSV文件实现接口批量测试
首先要理解为什么要进行批量测试,当我们在工作中进行接口测试时,项目的接口肯定不止一个,而是很多很多,而且每个接口都需要进行正确参数,错误参数,参数为空,特殊字符等方式来测试接口是否能够正确返回所需的响 ...
- PAT归纳总结——关于二叉树的一些总结
今天是6月26日到下个月的这个时候已经考过试了,为了让自己考一个更高的分数,所以我打算把PAT的相关题型做一个总结.目前想到的方法就是将相关的题型整理到一起然后,针对这种题型整理出一些方法. 二叉树的 ...
- postman传入cookie的三种方式
1.直接在环境变量里面设置 2.在登录接口的tests,把返回值的cookie设置为全局变量 3.直接在cookie里面传入
- 05- web网站链接测试与XENU工具使用
什么是链接 链接也叫超链接,是指从某一个网页元素指向另一个目标的连接关系,这个目标可以是另一个网站的网页,可以是本网站的一个网页,可以使同一个网页的不同位置,还可以是一个图片,一个视频,一个文件甚至是 ...
- system , DOS 命令
其实C语言也可以控制电脑关机什么的啊,以前竟然无知的连这个都不知道.悲哀啊.让各路大牛尽情嘲笑啊.. #include<stdio.h> #include<stdlib.h> ...
- Linux下Apache服务的部署和配置
目录 Apache服务的安装 yum源安装: 目录文件 源码包安装: 目录文件: Apache中添加对php的支持 Apache中添加php对mysql数据库的支持 Apache服务的高级配置 1:配 ...
- Linux中的DHCP服务
目录 DHCP DHCP的报文类型 DHCP的部署 DHCP中继(DHCP代理) DHCP DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是一个局 ...
- HashSet添加操作底层判读(Object类型)
Object类型添加操作判读 第一步:程序首先创建一个Object泛型的Set数组,这里用到了上转型: 第二步:执行object里面的add添加方法,传进的值为"JAVA": 首先 ...
- Linux的三剑客
首先,需要介绍一下管道和正则表达式,因为它经常和Linux三剑客一起使用. 一.管道Linux 提供管道符"|",将两个命令隔开,管道符左边命令的输出作为管道符右边命令的输入. c ...