天天看點

Java異常及相關調用性能測試問題測試場景結論測試代碼

見到有人讨論java裡的異常性能是好是壞,在業務代碼裡要不要用異常處理.

例如一些請求參數,到底是應該人工用if/else判斷,還是通過異常統一處理,還是通過注解校驗,抑或是其他方式呢?

這些方式對系統的性能會有什麼實際影響呢?

一般認為,java異常之是以慢,是因為需要擷取目前的運作棧資訊,而異常機制本身是正常性能消耗.

進而想到,一些通用的日志架構,比如log4j,logback,都是通過運作棧擷取抛出異常的代碼方法和運作行數的,官方文檔也标注此類資訊的列印會有較大的性能消耗.

而且我們也是通過類似的方式對jws的日志進行了增強.

那麼性能到底會有有多差?

運作環境

jdk8 + idea 2016

mac mini 2014 中配(硬碟非常慢,如有其它運作結果請聯系)

非空機運作,是以結果不是非常準确

jvm配置<code>-client -xcomp -xmx1024m -xms1024m</code>,每次調用完後主動通知gc一次

編譯模式跟解析模式的結果相差非常大

一般都說,使用異常之是以慢,是因為擷取運作棧慢,那到底有多慢,慢在哪裡,如果不擷取異常棧的話速度又如何?

通過<code>throwable</code>的源碼可以得知,大部分的夠着函數裡都會調用<code>java.lang.throwable#fillinstacktrace()</code>方法設定目前運作的異常棧,而這個方法慢的原因有:

方法本身是<code>synchronized</code>,也就是對象級的同步

這個方法需要有一個native調用

需要擷取目前運作棧的所有資訊

另外,throwable的很多方法也是<code>synchronized</code>的.

(不過線程棧資訊本身有緩存,理論上第二次調用會快一些,而且異常一般不會在多個線程中處理)

是以理論上來說,隻要把異常棧填充這一步去掉,異常對象應該是跟普通對象是差不多的.

剛好throwalbe及各種exception都有一個特殊的構造函數,可以跳過異常棧的擷取.

普通對象

hashmap

省略異常棧

普通異常

1000000個對象

35

38

48

1444

10000000個對象

30

62

99

10525

100000000個對象

269

593

936

112262

可以看到,關閉異常棧擷取後,異常對象的建立跟普通對象基本一緻,而擷取異常棧則多了不止一個數量級的耗時.

10個線程,每個線程10000000個對象

223

728

534

73946

100個線程,每個線程1000000個對象

251

605

533

85106

500個線程,每個線程200000個對象

264

780

793

68589

在并行模式下的結果跟串行模式幾乎一緻.

如果異常對象的建立的性能沒有問題,那麼try/catch塊呢?

沒有try/catch

循環外try/catch

循環内try/catch

循環内3次try/catch

運作10000000次

10

12

9

13

運作100000000次

43

49

53

45

運作500000000次

221

222

229

可以看到,當沒有發生異常時,try/catch并不消耗性能.

那麼,對異常的throw/catch需要消耗多少性能呢?

沒有throw/catch

throw/catch

11

36

51

113

252

519

而當抛出異常時,try/catch本身的性能消耗也隻是普通的代碼性能消耗.

通用的日志架構裡都是通過運作棧去擷取日志調用代碼的方法名/行數等資訊的,而且通過上述測試可以知道,這種方式會對性能代碼比較大的影響.

通過往某個檔案列印日志對運作資訊擷取的性能做測試.(使用非緩存式列印,一般日志架構預設都沒有使用緩存列印)

普通日志列印

運作資訊日志列印

列印10000行

297

列印100000行

535

2132

列印500000行

2576

8598

在串行模式下,通過運作棧擷取調用資訊确實會比普通的日志列印耗時多,可是可能因為磁盤io關系,性能差異沒有異常對象與普通對象的差異大.

10個線程,每個線程列印100000行

7199

19511

20個線程,每個線程列印50000行

7600

20053

50個線程,每個線程列印20000行

7832

19031

在并發環境下,由于性能進一步被檔案io限制,性能差異進一步縮小.

以上所有的測試都隻是驗證在一個非常純粹的環境下的性能表現,其中甚至會有jvm的一些優化措施.

而實際業務進行中,會有架構,網絡,業務處理等多種因素會影響系統的性能.

是以通過在本機搭建的一個基于jws的web工程來模拟實際的業務服務,通過ab測試相關的場景性能.

建立一個有3個參數的請求,通過ab工具測試if/else和try/catch兩種參數校驗方式對性能的影響.

(資料機關:qps)

if/else參數校驗

try/catch優化異常參數校驗

try/catch普通異常參數校驗

串行,5000請求

166.140

163.773

164.636

10并發,5000請求

206.59

206.22

203.06

50并發,5000請求

205.74

205.64

202.80

根據統計平台,生産環境80%請求的耗時基本都在10ms附近,測試用例通過<code>sleep</code>的方式模拟每次請求有10ms的業務處理.

(考慮到機器問題,在本機器上10ms的業務處理應該算是一個很小的值了)

每次請求列印10行日志

每次請求列印20行日志

每次請求列印50行日志

普通日志

56.67

54.85

49.76

運作上線文日志

51.39

47.44

37.10

59.54

57.27

52.78

55.81

48.96

38.57

雖然即使經過優化後的異常性能也有一定的消耗,可是在異常校驗這種場景下,一來因為發生頻率小,二來因為執行次數少(一個請求隻會出現一次try/catch),對業務請求的性能幾乎沒有影響.

而通過運作棧實作的日志增強,因為日志列印本身的高頻率,是以對業務系統性能有一定的影響.

(此用例基于jws,非jws環境無法運作)