麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

關(guān)注性能:宏性能基準測試

2019-11-18 15:08:07
字體:
供稿:網(wǎng)友

  字節(jié)碼提供了應(yīng)用程序性能的線索
級別:中級

Jack Shirazi (jack@javaPerformanceTuning.com),董事,JavaPerformanceTuning.com
Kirk Pepperdine ([email protected]),首席技術(shù)官,JavaPerformanceTuning.com

熱衷于 Java 性能的 Jack Shirazi 和 Kirk Pepperdine ?? JavaPerformanceTuning.com 的董事和 CTO ?? 跟蹤遍布 Internet 上的性能討論,探究是什么在困擾著開發(fā)人員。在瀏覽 Usenet 新聞組 comp.lang.java 時,他們碰到了幾個有意思的底層性能調(diào)整問題。在 關(guān)注性能 的這篇文章中,他們對字節(jié)碼作了一些分析,檢驗并回答了其中的一些問題。
盡管沒有專門針對 Java 性能的 Usenet 討論組,但是有許多關(guān)于性能調(diào)整和優(yōu)化的討論。這些討論中很大一部分基于從宏性能基準測試中得到的結(jié)果,所以在本月的專欄中我們也預(yù)備談?wù)撚嘘P(guān)宏基準測試(microbenchmarking)的好處和不足之處。

前置還后置?
有一個問題非凡引起我們注重:哪一種運算更快:i++ 還是 ++i?在我們?yōu)g覽過的幾乎每一個論壇中都可以看到以不同的形式提出的這個問題。雖然這個問題很簡單,但是看來沒有一個絕對的答案。

首先介紹一下它們的區(qū)別,++i 使用前置增量運算符,而 i++ 使用后置增量運算符。雖然它們都增加變量 i,但是前置增量運算符在增量運算之前返回 i 的值,而后置增量運算符在增量運算之后返回值 i。一個簡單的測試程序展示了這種區(qū)別:

public class Test {
public static void main(String[] args) {
int PRe = 1;
int post = 1;
System.out.println("++pre = " + (++pre));
System.out.println("post++ = " + (post++));
}
}

運行 Test 類生成以下的輸出:

++pre = 2
post++ = 1

宏基準測試
難道不能試著反復(fù)運行每次運算,并觀察哪一種運算有更快的運行時嗎?簡單的回答是能,但是危險在于宏基準測試并不總是測量您想要它們測量的內(nèi)容。相當(dāng)多的時候,即時(JIT)編譯器的優(yōu)化和變化掩蓋了底層性能中所有可檢測的差異。例如,一個這種測試顯示第二個 i++ 運算比第一個 ++i 測試更快。但是改變測試順序顯示正好相反的結(jié)果!從這里我們只能得出測試方法有缺陷的結(jié)論。進一步的調(diào)查表明,這種令人困惑的結(jié)果來源于在第一次測試時發(fā)生的 HotSpot 優(yōu)化。這些優(yōu)化有雙重效果:使第一次運行增加了額外的開銷,并去掉了第二次運行時的解釋成本。

宏基準測試的其他變化,如在發(fā)生了 JIT 啟動成本后重復(fù)測試,在反復(fù)運行時只能給出不確定的結(jié)果。它可能告訴我們兩種運算符在速度上沒有區(qū)別,但是我們對此不能確定。

iinc 字節(jié)碼運算符
Heinz Kabutx 博士在其新聞信 The Java Specialists Newsletter 中,問他的讀者哪一個更快: i++、++i 還是 i+=1?在 Issue 64 中,他報告說有一位讀者用一種簡單的技術(shù)回答了他的問題:查看編譯的字節(jié)碼。事實上,他考察了四種增量語句:

++i;
i++;
i -= -1;
i += 1;

可以用 Java SDK 所帶的反匯編程序 javap 很輕易地分析編譯的字節(jié)碼。這四種增量語句的每一種得到的字節(jié)碼都是 iinc 1 1。

iinc 運算符有兩個參數(shù)。第一個參數(shù)指定變量在 JVM 的局部變量表中的索引,第二個參數(shù)指定變量的增量值。

這是不是給了我們一個明確的回答?無論如何,假如不同的源代碼編譯為同樣的字節(jié)碼,那么在速度上沒有區(qū)別,是不是?

運算符上下文
那么,假如代碼片斷都編譯為同樣的字節(jié)碼,使用不同的運算符的意義何在?好,讓我們回過頭看前置增量符和后置增量符。要害的一點是什么時候訪問變量。假如不訪問變量,那么這些運算符之間就沒有什么區(qū)別。語句 i++ 和 ++i 本身在功能上是一樣的。不過,語句 j=i++ 和 j=++i 在功能上是 不一樣的。我們需要分析在額外的賦值上下文中的字節(jié)碼。考慮這兩個類似的方法:

public static int preIncrement() {
int i = 0, j;
j = ++i;
return j;
}
public static int postIncrement() {
int i = 0, j;
j = i++;
return j;
}

反匯編 preIncrement() 和 postIncrement() 得到下面的字節(jié)碼:

Method int preIncrement()
0 iconst_0
1 istore_0
2 iinc 0 1
5 iload_0
6 istore_1
7 iload_1
8 ireturn

Method int postIncrement()
0 iconst_0
1 istore_0
2 iload_0
3 iinc 0 1
6 istore_1
7 iload_1
8 ireturn

現(xiàn)在我們 可以 看到這兩種方法間的區(qū)別:preIncrement() 返回 1,而 postIncrement() 返回 0。讓我們分析字節(jié)碼,更好地理解這種區(qū)別。首先,我們將解釋在反匯編的代碼中可以看到的不同字節(jié)碼運算符。

字節(jié)碼運算:i=0
iconst_0 運算符將整數(shù) iconst_0 推到堆棧上。要完全理解這一點,請記住 JVM 模擬一個基于堆棧的 CPU(假如您以前沒接觸過堆棧,請參閱 java.util.Stack 類文檔)。JVM 在需要以后對某些東西進行操作時,先將它們推到堆棧中,在預(yù)備對它們進行操作時彈出它們。

在 Java 語言中有幾種不同的數(shù)據(jù)類型,對于不同的數(shù)據(jù)類型有不同的字節(jié)碼運算符。對于某些特定的優(yōu)化,值 -1、0、1、2、3、4 和 5 都有專門的字節(jié)碼。假如我們不是處理這些值,那么編譯器會生成 bipush 字節(jié)碼運算,將一個特定的整數(shù)推到堆棧上(例如,假如方法的第一條語句是 int i = -2,那么第一個字節(jié)碼將會 bipush -2)。

下一條語句 istore_0 看上去可能像另一個處理整數(shù) -1 到 5 的非凡字節(jié)碼,但是事實上,這次 _0 指向一個到局部變量表的索引。JVM 維護一個局部于方法的變量表,字節(jié)碼 istore 在堆棧的頂部彈出這個值,并將這個值儲存到局部變量表中。在這里我們用的是 istore_0,所以這個值儲存在表的索引 0 處。

所有這些解釋針對的是“i=0”的Java 字節(jié)碼,它被轉(zhuǎn)換為字節(jié)碼:

0 iconst_0
1 istore_0

更多的字節(jié)碼運算
現(xiàn)在我們知道了堆棧和局部變量表,我們可以更快地討論其他字節(jié)碼。正如我們前面說的,字節(jié)碼 iinc 0 1 在局部變量表索引 0 處增量值 1,iload_0 將局部變量表索引 0 處的值推到椎棧中,而 ireturn 從堆棧中彈出這個值,并將它推到調(diào)用方法的操作數(shù)堆棧上。下面的表 1 概括了字節(jié)碼。

表 1. 字節(jié)碼

字節(jié)碼 描述
iconst_0 將 0 推到堆棧中
iconst_1 將 1 推到堆棧中
istore_0 從堆棧中彈出這個值,并將它存儲到局部變量表的索引 0 處
istore_1 從堆棧中彈出這個值,并將它存儲到局部變量表的索引 1 處
iload_0 將局部變量表索引 0 處的值推到堆棧中
iload_1 將局部變量表索引 1 處的值推到堆棧中
iadd 從操作數(shù)堆棧中彈出兩個整數(shù)并讓它們相加。將得到的整數(shù)推回堆棧中
iinc 0 1 局部變量表索引 0 處的變量加 1
ireturn 從堆棧中彈出值并將它推到調(diào)用方法的操作數(shù)棧中。退出方法

比較方法
現(xiàn)在,讓我們再看一下這些反匯編的字節(jié)碼。我們將用 lvar 表示局部變量表,就像它是一個 Java 數(shù)組,并對字節(jié)碼加上注釋:

Method int preIncrement()
0 iconst_0 //push 0 onto the stack
1 istore_0 //pop 0 from the stack and store it at lvar[0], i.e. lvar[0]=0
2 iinc 0 1 //lvar[0] = lvar[0]+1 which means that now lvar[0]=1
5 iload_0 //push lvar[0] onto the stack, i.e. push 1
6 istore_1 //pop the stack (value at top is 1) and store at it lvar[1], i.e. lvar[1]=1
7 iload_1 //push lvar[1] onto the stack, i.e. push 1
8 ireturn //pop the stack (value at top is 1) to the invoking method i.e. return 1

Method int postIncrement()
0 iconst_0 //push 0 onto the stack
1 istore_0 //pop 0 from the stack and store it at lvar[0], i.e. lvar[0]=0
2 iload_0 //push lvar[0] onto the stack, i.e. push 0
3 iinc 0 1 //lvar[0] = lvar[0]+1 which means that now lvar[0]=1
6 istore_1 //pop the stack (value at top is 0) and store at it lvar[1], i.e. lvar[1]=0
7 iload_1 //push lvar[1] onto the stack, i.e. push 0
8 ireturn //pop the stack (value at top is 0) to the invoking method i.e. return 0

現(xiàn)在,希望您能更清楚地了解所發(fā)生的事情,以及方法之間的一些功能差別。惟一的差別是兩個方法的第三個和第四個字節(jié)碼交換了。注釋的字節(jié)碼清楚表明,在 postIncrement() 方法中,iinc 運算完全是多余的,因為從這一點起,不再使用被更新的局部變量元素 lvar[0]。對于這個特定的方法,一個優(yōu)化 JIT 編譯程序可以完全去掉這種字節(jié)碼運算。所以在這種特定情形中,postIncrement() 方法可能有比 preIncrement() 操作更少的字節(jié)碼運算,從而使它更加高效。但是在大多數(shù)使用后置增量運算符的情況下,增量運算是不能優(yōu)化的。

那么誰更快呢?
我們學(xué)到了什么?是的,假如語句只有 ++i 和 i++ ,那么它們之間沒有區(qū)別。只有在存在額外的賦值時,編譯的字節(jié)碼才會有區(qū)別。

在賦值的上下文中,比較前置增量運算符或者后置增量運算符的使用有可能得到不同的運行時。但是使用哪種運算的功能結(jié)果都不太可能是一樣的。記住,在我們這里的例子里,方法實際上返回不同的值,它取決于我們是使用前置增量運算符還是后置增量運算符。在一個普通程序中,其中一種變化可能會成為一個缺陷。

結(jié)束語
在過去,我們可以根據(jù)一組運算的語言表達對它們的成本進行測量。這是因為這些運算到底層運行時環(huán)境的轉(zhuǎn)換總是靜態(tài)的,這在 Java 運行時中是不成立的。Java 運行時可以動態(tài)優(yōu)化運行的代碼,這是一種非凡強大的功能。盡管這種功能還沒有使我們完全不能進行宏性能基準測試,但是它導(dǎo)致我們在使用這種技術(shù)時需要更加當(dāng)心。

參考資料

閱讀 Jack Shirazi 和 Kirk Pepperdine 的全部 關(guān)注性能 系列。

Greg Travis 的“如何封鎖您的(或打開別人的) Java 代碼”(developerWorks,2001 年 5 月)提供了有關(guān)反編譯一個 Java 類文件的信息。

利用 The Jikes Research Virtual Machine (developerWorks,2000 年 2 月)了解更多有關(guān) IBM 對高性能 JVM 的研究。

Click 博士在 JavaOne 2003 上展示了 High Performance Computing with HotSpot Server Compiler (PDF)。

The Java HotSpot Virtual Machine, v1.4.1 (java.sun.com,2002 年 9 月)是有關(guān) JVM HotSpot 技術(shù)的官方白皮書。

Jack Shirazi 的“Micro-Tuning Step-by-Step”(ONJava,2002 年 3 月)提供了對宏性能基準測試的實用建議。

在 developerWorks Java 技術(shù)專區(qū) 可以找到數(shù)百篇有關(guān) Java 編程各個方面的文章。

關(guān)于作者
Jack Shirazi 是 JavaPerformanceTuning.com 的董事,也是 Java Performance Tuning (O′Reilly)一書的作者。

Kirk Pepperdine 是 Java Performance Tuning.com 的首席技術(shù)官,并且在過去 15 年一直關(guān)注對象技術(shù)和性能調(diào)優(yōu)。Kirk 是 ANT Developer′s Handbook 一書的合著者。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 伊人网站| 精品一区二区三区日本 | a免费毛片 | 欧美日韩国产成人在线 | 精选久久| 久久精品操 | 激情久久精品 | 福利在线小视频 | 免费看成人av | 特一级毛片| 国产欧美亚洲精品 | 男女无套免费视频 | 嗯~啊~弄嗯~啊h高潮视频 | 亚洲综合色视频在线观看 | 精品国产一区二区三区在线 | 97伦理 | 成人啪啪18免费网站 | 欧美a∨一区二区三区久久黄 | 逼片 | 欧美成人一区二区视频 | 在线观看国产 | 久久免费视频一区 | 久久艹一区 | 国产精品视频二区不卡 | 精品国产91久久久久久久 | 国产91九色 | 久久久久夜色精品国产老牛91 | 蜜桃网站在线观看 | 91网址在线观看 | 一级一级一级一级毛片 | 羞羞网站在线看 | 午夜视频你懂的 | 欧美成人三级视频 | 欧美性videofree精品 | 免费在线观看国产精品 | 黄色成年在线观看 | 国产九色在线播放九色 | 婷婷久久综合九色综合色多多蜜臀 | 国产精品99一区二区 | 成人在线免费视频播放 | 成人在线免费小视频 |