如果您頻繁存取變量,就需要考慮從何處存取這些變量。變量是
static |
變量,還是堆棧變量,或者是類的實例變量?變量的存儲位置對存取它的代碼的性能有明顯的影響?例如,請考慮下面這段代碼:
class StackVars { private int instVar; private static int staticVar; //存取堆棧變量 void stackAccess(int val) { int j=0; for (int i=0; i<val; i++) j += 1; } //存取類的實例變量 void instanceAccess(int val) { for (int i=0; i<val; i++) instVar += 1; } //存取類的 static 變量 void staticAccess(int val) { for (int i=0; i<val; i++) staticVar += 1; } } |
這段代碼中的每個方法都執行相同的循環,并反復相同的次數。唯一的不同是每個循環使一個不同類型的變量遞增。方法
stackAccess |
使一個局部堆棧變量遞增,
instanceAccess
使類的一個實例變量遞增,而 staticAccess
使類的一個 static
變量遞增。
instanceAccess
和 staticAccess
的執行時間基本相同。但是, stackAccess
要快兩到三倍。存取堆棧變量如此快是因為,JVM 存取堆棧變量比它存取 static
變量或類的實例變量執行的操作少。請看一下為這三個方法生成的字節碼:
Method void stackAccess(int) 0 iconst_0 //將 0 壓入堆棧。 1 istore_2 //彈出 0 并將它存儲在局部分變量表中索引為 2 的位置 (j)。 2 iconst_0 //壓入 0。 3 istore_3 //彈出 0 并將它存儲在局部變量表中索引為 3 的位置 (i)。 4 goto 13 //跳至位置 13。 7 iinc 2 1 //將存儲在索引 2 處的 j 加 1。 10 iinc 3 1 //將存儲在索引 3 處的 i 加 1。 13 iload_3 //壓入索引 3 處的值 (i)。 14 iload_1 //壓入索引 1 處的值 (val)。 15 if_icmplt 7 //彈出 i 和 val。如果 i 小于 val,則跳至位置 7。 18 return //返回調用方法。 Method void instanceAccess(int) 0 iconst_0 //將 0 壓入堆棧。 1 istore_2 //彈出 0 并將它存儲在局部變量表中索引為 2 的位置 (i)。 2 goto 18 //跳至位置 18。 5 aload_0 //壓入索引 0 (this)。 6 dup //復制堆棧頂的值并將它壓入。 7 getfield #19 <Field int instVar> //彈出 this 對象引用并壓入 instVar 的值。 10 iconst_1 //壓入 1。 11 iadd //彈出棧頂的兩個值,并壓入它們的和。 12 putfield #19 <Field int instVar> //彈出棧頂的兩個值并將和存儲在 instVar 中。 15 iinc 2 1 //將存儲在索引 2 處的 i 加 1。 18 iload_2 //壓入索引 2 處的值 (i)。 19 iload_1 //壓入索引 1 處的值 (val)。 20 if_icmplt 5 //彈出 i 和 val。如果 i 小于 val,則跳至位置 5。 23 return //返回調用方法。 Method void staticAccess(int) 0 iconst_0 //將 0 壓入堆棧。 1 istore_2 //彈出 0 并將它存儲在局部變量表中索引為 2 的位置 (i)。 2 goto 16 //跳至位置 16。 5 getstatic #25 <Field int staticVar> //將常數存儲池中 staticVar 的值壓入堆棧。 8 iconst_1 //壓入 1。 9 iadd //彈出棧頂的兩個值,并壓入它們的和。 10 putstatic #25 <Field int staticVar> //彈出和的值并將它存儲在 staticVar 中。 13 iinc 2 1 //將存儲在索引 2 處的 i 加 1。 16 iload_2 //壓入索引 2 處的值 (i)。 17 iload_1 //壓入索引 1 處的值 (val)。 18 if_icmplt 5 //彈出 i 和 val。如果 i 小于 val,則跳至位置 5。 21 return //返回調用方法。 |
查看字節碼揭示了堆棧變量效率更高的原因。JVM 是一種基于堆棧的虛擬機,因此優化了對堆棧數據的存取和處理。所有局部變量都存儲在一個局部變量表中,在 Java 操作數堆棧中進行處理,并可被高效地存取。存取 static
變量和實例變量成本更高,因為 JVM 必須使用代價更高的操作碼,并從常數存儲池中存取它們。(常數存儲池保存一個類型所使用的所有類型、字段和方法的符號引用。)
通常,在第一次從常數存儲池中訪問 static
變量或實例變量以后,JVM 將動態更改字節碼以使用效率更高的操作碼。盡管有這種優化,堆棧變量的存取仍然更快。
考慮到這些事實,就可以重新構建前面的代碼,以便通過存取堆棧變量而不是實例變量或 static
變量使操作更高效。請考慮修改后的代碼:
class StackVars { //與前面相同... void instanceAccess(int val) { int j = instVar; for (int i=0; i<val; i++) j += 1; instVar = j; } void staticAccess(int val) { int j = staticVar; for (int i=0; i<val; i++) j += 1; staticVar = j; } } |
方法 instanceAccess
和 staticAccess
被修改為將它們的實例變量或 static
變量復制到局部堆棧變量中。當變量的處理完成以后,其值又被復制回實例變量或 static
變量中。這種簡單的更改明顯提高了 instanceAccess
和 staticAccess
的性能。這三個方法的執行時間現在基本相同, instanceAccess
和 staticAccess
的執行速度只比 stackAccess
的執行速度慢大約 4%。
這并不表示您應該避免使用 static
變量或實例變量。您應該使用對您的設計有意義的存儲機制。例如,如果您在一個循環中存取 static
變量或實例變量,則您可以臨時將它們存儲在一個局部堆棧變量中,這樣就可以明顯地提高代碼的性能。這將提供最高效的字節碼指令序列供 JVM 執行
轉載自ibm