天天看點

Java異常的性能分析

在java中抛異常的性能是非常差的。通常來說,抛一個異常大概會消耗100到1000個時鐘節拍。

通常是出現了意想不到的錯誤,我們才會往外抛異常。也就是說,我們肯定不希望一個程序一秒鐘就抛出上千個異常。不過有時候你确實會碰到有些方法把異常當作事件一樣往外抛。我們在這篇文章中已經看到一個這樣的典範):sun.misc.base64decoder之是以性能很差就是因為它通過抛異常來對外請求道,”我還需要更多的資料“:

1

2

3

4

5

6

7

8

9

10

<code>at java.lang.throwable.fillinstacktrace(throwable.java:-1)</code>

<code>at java.lang.throwable.fillinstacktrace(throwable.java:782)</code>

<code>- locked &lt;0x6c&gt; (a sun.misc.cestreamexhausted)</code>

<code>at java.lang.throwable.&lt;init&gt;(throwable.java:250)</code>

<code>at java.lang.exception.&lt;init&gt;(exception.java:54)</code>

<code>at java.io.ioexception.&lt;init&gt;(ioexception.java:47)</code>

<code>at sun.misc.cestreamexhausted.&lt;init&gt;(cestreamexhausted.java:30)</code>

<code>at sun.misc.base64decoder.decodeatom(base64decoder.java:117)</code>

<code>at sun.misc.characterdecoder.decodebuffer(characterdecoder.java:163)</code>

<code>at sun.misc.characterdecoder.decodebuffer(characterdecoder.java:194)</code>

如果你用一個數字開頭,字母結尾的字元串來運作下這篇文章裡面的pack方法,你也會碰到類似的情況。我們來看下用那個方法打包"12345"和"12345a"需要多長的時間:

<code>made 100.000.000 iterations</code><code>for</code> <code>string</code><code>'12345'</code> <code>: time = 12.109 sec</code>

<code>made 1.000.000 iterations</code><code>for</code> <code>string</code><code>'12345a'</code> <code>: time = 21.764 sec</code>

可以看到,’12345a'疊代的次數要比‘12345’少100倍。也就是說這個方法處理'12345a'慢了差不多200倍。大多數的處理時間都在填充異常的棧跟蹤資訊了:

11

12

13

14

<code>        </code><code>at java.lang.throwable.fillinstacktrace(throwable.java:782)</code>

<code>        </code><code>- locked &lt;0x87&gt; (a java.lang.numberformatexception)</code>

<code>        </code><code>at java.lang.throwable.&lt;init&gt;(throwable.java:265)</code>

<code>        </code><code>at java.lang.exception.&lt;init&gt;(exception.java:66)</code>

<code>        </code><code>at java.lang.runtimeexception.&lt;init&gt;(runtimeexception.java:62)</code>

<code>        </code><code>at java.lang.illegalargumentexception.&lt;init&gt;(illegalargumentexception.java:53)</code>

<code>        </code><code>at java.lang.numberformatexception.&lt;init&gt;(numberformatexception.java:55)</code>

<code>        </code><code>at java.lang.numberformatexception.forinputstring(numberformatexception.java:65)</code>

<code>        </code><code>at java.lang.long.parselong(long.java:441)</code>

<code>        </code><code>at java.lang.long.valueof(long.java:540)</code>

<code>        </code><code>at com.mvorontsov.javaperf.strconvtests.pack(strconvtests.java:69)</code>

<code>        </code><code>at com.mvorontsov.javaperf.strconvtests.test(strconvtests.java:38)</code>

<code>        </code><code>at com.mvorontsov.javaperf.strconvtests.main(strconvtests.java:29)</code>

通過手動解析數字,我們可以很容易提升pack方法的性能。不過不要忘了——不到萬不得已,不要随便優化。如果你隻是解析幾個輸入參數而已—— keep it simple,就用jdk的方法就好了。如果你要解析大量的消息,又必須調用一個類似pack這樣的方法——那确實得去優化一下了。

新的pack方法和舊的實作差不太多——把一個字元串轉化成一個盡可能小的character/integer/long/double/string類型,使得result.tostring().equals(orginalstring)為true。

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

<code>public static object strtoobject( final string str )</code>

<code>{</code>

<code>    </code><code>if</code> <code>( str ==</code><code>null</code> <code>|| str.length() &gt; 17 )</code>

<code>    </code><code>{ </code><code>//out of long range</code>

<code>        </code><code>return</code> <code>str;</code>

<code>    </code><code>}</code>

<code>    </code><code>if</code> <code>( str.equals(</code><code>""</code> <code>) )</code>

<code>        </code><code>return</code> <code>""</code><code>;</code><code>//ensure interned string is returned</code>

<code>    </code><code>if</code> <code>( str.length() == 1 )</code>

<code>        </code><code>return</code> <code>str.charat( 0 );</code><code>//return character</code>

<code>    </code><code>//if starts with zero - support only "0" and "0.something"</code>

<code>    </code><code>if</code> <code>( str.charat( 0 ) ==</code><code>'0'</code> <code>)</code>

<code>    </code><code>{</code>

<code>        </code><code>if</code> <code>( str.equals(</code><code>"0"</code> <code>) )</code>

<code>            </code><code>return</code> <code>0;</code>

<code>        </code><code>if</code> <code>( !str.startswith(</code><code>"0."</code> <code>) ) </code><code>//this may be a double</code>

<code>            </code><code>return</code> <code>str;</code>

<code> </code> 

<code>    </code><code>long res = 0;</code>

<code>    </code><code>int sign = 1;</code>

<code>    </code><code>for</code> <code>( int i = 0; i &lt; str.length(); ++i )</code>

<code>        </code><code>final char c = str.charat( i );</code>

<code>        </code><code>if</code> <code>( c &lt;=</code><code>'9'</code> <code>&amp;&amp; c &gt;=</code><code>'0'</code> <code>)</code>

<code>            </code><code>res = res * 10 + ( c -</code><code>'0'</code> <code>);</code>

<code>        </code><code>else</code> <code>if</code> <code>( c ==</code><code>'.'</code> <code>)</code>

<code>        </code><code>{</code>

<code>            </code><code>//too lazy to write a proper double parser, use jdk one</code>

<code>            </code><code>try</code>

<code>            </code><code>{</code>

<code>                </code><code>final double val = double.valueof( str );</code>

<code>                </code><code>//check if value converted back to string equals to an original string</code>

<code>                </code><code>final string reverted = val.tostring();</code>

<code>                </code><code>return</code> <code>reverted.equals( str ) ? val : str;</code>

<code>            </code><code>}</code>

<code>            </code><code>catch</code> <code>( numberformatexception ex )</code>

<code>                </code><code>return</code> <code>str;</code>

<code>        </code><code>}</code>

<code>        </code><code>else</code> <code>if</code> <code>( c ==</code><code>'-'</code> <code>)</code>

<code>            </code><code>if</code> <code>( i == 0 )</code>

<code>                </code><code>sign = -1;</code><code>//switch sign at first position</code>

<code>            </code><code>else</code>

<code>                </code><code>return</code> <code>str;</code><code>//otherwise it is not numeric</code>

<code>        </code><code>else</code> <code>if</code> <code>( c ==</code><code>'+'</code> <code>)</code>

<code>                </code><code>sign = 1;</code><code>//sign at first position</code>

<code>        </code><code>else</code> <code>//non-numeric</code>

<code>    </code><code>//cast to int if value is in int range</code>

<code>    </code><code>if</code> <code>( res &lt; integer.max_value )</code>

<code>        </code><code>return</code> <code>( int ) res * sign;</code>

<code>    </code><code>//otherwise return long</code>

<code>    </code><code>return</code> <code>res * sign;</code>

<code>}</code>

很驚訝吧,新的方法解析數字比jdk的實作快多了!很大一個原因是因為jdk在解析的最後,調用了一個支援的解析方法,像這樣:

public static int parseint( string s, int radix ) throws numberformatexception

新的方法和舊的相比(注意方法調用的次數——對于非數字串pack隻調用了1百萬次,而别的情況能調用到千萬級别):

<code>pack: made 100.000.000 iterations</code><code>for</code> <code>string</code><code>'12345'</code> <code>: time = 12.145 sec</code>

<code>pack: made 1.000.000 iterations</code><code>for</code> <code>string</code><code>'12345a'</code> <code>: time = 23.248 sec</code>

<code>strtoobject: made 100.000.000 iterations</code><code>for</code> <code>string</code><code>'12345'</code> <code>: time = 6.311 sec</code>

<code>strtoobject: made 100.000.000 iterations</code><code>for</code> <code>string</code><code>'12345a'</code> <code>: time = 5.807 sec</code>

千萬不要把異常當成傳回碼一樣用,或者當作可能發生的事件(尤其是和io無關的方法)往外抛。抛異常的代價太昂貴了,對于一般的方法,至少要慢百倍以上。

如果你每條資料都需要解析,又經常會出現非數值串的時候,盡量不要用number子類型的parse*/valueof這些方法。為了性能考慮,你應當手動解析它們。

特别說明:尊重作者的勞動成果,轉載請注明出處哦~~~http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt276