替换对象所有字段_JVM字段访问优化
只有經歷過地獄般的磨礪,才能練就創造天堂的力量;只有流過血的手指,才能彈出世間的絕響。——泰戈爾
在實際中,Java程序中的對象或許 本身就是逃逸 的,或許因為 方法內聯不夠徹底 而被即時編譯器 當成是逃逸 的,這兩種情況都將導致即時編譯器 無法進行標量替換 ,這時,針對對象字段訪問的優化顯得更為重要。
static int bar(Foo o, int x)?{
????o.a = x; return o.a;
}
1.對象o是傳入參數, 不屬于逃逸分析的范圍 (JVM中的逃逸分析針對的是 新建對象 )
2.該方法會將所傳入的int型參數x的值存儲至實例字段Foo.a中,然后再讀取并返回同一字段的值
3.這段代碼涉及 兩次 內存訪問操作:存儲和讀取實例字段Foo.a
代碼可以手工優化成如下
static int bar(Foo o, int x) {?
????o.a = x;?
????return x;?
}
即時編譯器也能作出類似的 自動優化
字段讀取優化
即時編譯器會優化 實例字段 和 靜態字段 的訪問,以 減少總的內存訪問次數
即時編譯器將 沿著控制流 ,緩存各個字段 存儲節點 將要存儲的值,或者字段 讀取節點 所得到的值
1.當即時編譯器 遇到對同一字段的讀取節點 時,如果緩存值還沒有失效,那么將讀取節點 替換 為該緩存值
2.當即時編譯器 遇到對同一字段的存儲節點 時,會 更新 所緩存的值
3.當即時編譯器遇到 可能更新 字段的節點時,它會采取 保守 的策略, 舍棄所有的緩存值
4.方法調用節點 :在即時編譯器看來,方法調用會執行 未知代碼
5.內存屏障節點 :其他線程可能異步更新了字段
樣例1
static int bar(Foo o, int x) {
????int y = o.a + x;
????return o.a + y;
}
實例字段Foo.a被讀取兩次,即時編譯器會將第一次讀取的值緩存起來,并且 替換 第二次的字段讀取操作,以 節省 一次內存訪問
static int bar(Foo o, int x) {?
????int t = o.a;?
????int y = t + x;?
????return t + y;?
}
樣例2
static int bar(Foo o, int x) {?
????o.a = 1;?
????if (o.a >= 0) ?return x;
????else ?return -x;?
}
字段讀取節點被替換成一個 常量 ,進一步觸發更多的優化
static int bar(Foo o, int x) {?
????o.a = 1;
????return x;?
}
樣例3
class Foo {
????boolean a;
????void bar() { ?
????????a = true; ?
????????while (a) {}?
????}?
????void whatever() {?
????a = false;?
????}?
}
即時編譯器會將while循環中讀取實例字段a的操作 直接替換為常量true
void bar() {?
????a = true;?
???while (true) {}?
}?
// 生成的機器碼將陷入這一死循環中 0x066b: mov r11,QWORD PTR [r15+0x70]?
// 安全點測試 0x066f: test DWORD PTR [r11],eax ?
// 安全點測試 0x0672: jmp 0x066b ? ??
// while (true)
1、可以通過 volatile 關鍵字標記實例字段a,以 強制 對a的讀取
2、實際上,即時編譯器將 在volatile字段訪問前后插入內存屏障節點
這些 內存屏障節點 將 阻止 即時編譯器 將屏障之前所緩存的值用于屏障之后的讀取節點之上
在X86_64平臺上,volatile字段讀取前后的內存屏障都是no-op
在 即時編譯過程中的屏障節點 ,還是會 阻止即時編譯器的字段讀取優化
強制在循環中使用 內存讀取指令 訪問實例字段Foo.a的最新值
3、同理, 加解鎖操作同樣也會阻止即時編譯器的字段讀取優化
字段存儲優化
如果一個字段先后被存儲了兩次,而且這 兩次存儲之間沒有對第一次存儲內容讀取 ,那么即時編譯器將 消除 第一個字段存儲
樣例1
class Foo {?
????int a = 0;
????void bar() {
?????????a = 1;
?????????a = 2;?
?????}?
}
即時編譯器將消除bar方法的冗余存儲
void bar() { a = 2; }
樣例2
即便在某個字段的兩個存儲操作之間讀取該字段,即時編譯器也可能在 字段讀取優化 的幫助下,將第一個存儲操作當作 冗余存儲
場景:例如兩個存儲操作之間隔著許多代碼,又或者因為 方法內聯 的原因,將兩個存儲操作納入到同一編譯單元里(如構造器中字段的初始化以及隨后的更新)
class Foo {?
????int a = 0;
????void bar() {
?????????a = 1;
?????????int t = a;
?????????a = t + 2;?
??????}?
}?
// 優化為?
class Foo {
????int a = 0;
????void bar() {
?????????a = 1;
?????????int t = 1;
?????????a = t + 2;
??????}
} // 進一步優化為?
class Foo {
????int a = 0;
????void bar() {
?????????a = 3;
?????}
}
如果所存儲的字段被標記為 volatile ,那么即時編譯器也 不能消除冗余存儲
死代碼消除
樣例1
int bar(int x, int y) {?
????int t = x*y;
????t = x+y;?
????return t;
}
沒有節點依賴于t的第一個值 x*y ,因此該乘法運算將被消除
int bar(int x, int y) {
????return x+y;
}
樣例2
int bar(boolean f, int x, int y) {
????????int t = x*y;
????????if (f) ?t = x+y;
????????return t;
}
部分程序路徑上有冗余存儲(f=true),該路徑上的乘法運算將會被消除
int bar(boolean f, int x, int y) {
????????int t;
????????if (f) ?t = x+y;
????????else ?t = x*y;
????????return t;
}
樣例3
int bar(int x) {
????if (false) ?return x;
????else ?return -x;?
}
不可達分支指的是任何程序路徑都不可達到的分支,即時編譯器將 消除不可達分支
int bar(int x) { return -x; }
總結
今天介紹了即時編譯器關于字段訪問的優化方式,以及死代碼消除。
即時編譯器將沿著控制流緩存字段存儲、讀取的值,并在接下來的字段讀取操作時直接使用該緩存值。
這要求生成緩存值的訪問以及使用緩存值的讀取之間沒有方法調用、內存屏障,或者其他可能存儲該字段的節點。
即時編譯器還會優化冗余的字段存儲操作。如果一個字段的兩次存儲之間沒有對該字段的讀取操作、方法調用以及內存屏障,那么即時編譯器可以將第一個冗余的存儲操作給消除掉。
總結
以上是生活随笔為你收集整理的替换对象所有字段_JVM字段访问优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python语言用什么来表明每行代码的层
- 下一篇: cocos2d python文档_【Co