首页 > Java开发 > Java异常的性能分析

Java异常的性能分析

在Java中抛异常的性能是非常差的。通常来说,抛一个异常大概会消耗100到1000个时钟节拍。

通常是出现了意想不到的错误,我们才会往外抛异常。也就是说,我们肯定不希望一个进程一秒钟就抛出上千个异常。不过有时候你确实会碰到有些方法把异常当作事件一样往外抛。我们在这篇文章中已经看到一个这样的典范):sun.misc.BASE64Decoder之所以性能很差就是因为它通过抛异常来对外请求道,”我还需要更多的数据“:

[java]
  1. <br>at java.lang.Throwable.fillInStackTrace(Throwable.java:-1)  
  2. <br>at java.lang.Throwable.fillInStackTrace(Throwable.java:782)  
  3. <br>- locked <0x6c> (a sun.misc.CEStreamExhausted)  
  4. <br>at java.lang.Throwable.<init>(Throwable.java:250)  
  5. <br>at java.lang.Exception.<init>(Exception.java:54)  
  6. <br>at java.io.IOException.<init>(IOException.java:47)  
  7. <br>at sun.misc.CEStreamExhausted.<init>(CEStreamExhausted.java:30)  
  8. <br>at sun.misc.BASE64Decoder.decodeAtom(BASE64Decoder.java:117)  
  9. <br>at sun.misc.CharacterDecoder.decodeBuffer(CharacterDecoder.java:163)  
  10. <br>at sun.misc.CharacterDecoder.decodeBuffer(CharacterDecoder.java:194)  
  11. <br></init></init></init></init><!--0x6c-->  




如果你用一个数字开头,字母结尾的字符串来运行下这篇文章里面的pack方法,你也会碰到类似的情况。我们来看下用那个方法打包"12345"和"12345a"需要多长的时间:

[java]
  1. <br>Made 100.000.000 iterations for string '12345' : time = 12.109 sec  
  2. <br>Made 1.000.000 iterations for string '12345a' : time = 21.764 sec  
  3. <br>  




可以看到,’12345a'迭代的次数要比‘12345’少100倍。也就是说这个方法处理'12345a'慢了差不多200倍。大多数的处理时间都在填充异常的栈跟踪信息了:


[java]
  1. <br>at java.lang.Throwable.fillInStackTrace(Throwable.java:-1)  
  2. <br>        at java.lang.Throwable.fillInStackTrace(Throwable.java:782)  
  3. <br>        - locked <0x87> (a java.lang.NumberFormatException)  
  4. <br>        at java.lang.Throwable.<init>(Throwable.java:265)  
  5. <br>        at java.lang.Exception.<init>(Exception.java:66)  
  6. <br>        at java.lang.RuntimeException.<init>(RuntimeException.java:62)  
  7. <br>        at java.lang.IllegalArgumentException.<init>(IllegalArgumentException.java:53)  
  8. <br>        at java.lang.NumberFormatException.<init>(NumberFormatException.java:55)  
  9. <br>        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)  
  10. <br>        at java.lang.Long.parseLong(Long.java:441)  
  11. <br>        at java.lang.Long.valueOf(Long.java:540)  
  12. <br>        at com.mvorontsov.javaperf.StrConvTests.pack(StrConvTests.java:69)  
  13. <br>        at com.mvorontsov.javaperf.StrConvTests.test(StrConvTests.java:38)  
  14. <br>        at com.mvorontsov.javaperf.StrConvTests.main(StrConvTests.java:29)  
  15. <br></init></init></init></init></init><!--0x87-->  




通过手动解析数字,我们可以很容易提升pack方法的性能。不过不要忘了——不到万不得已,不要随便优化。如果你只是解析几个输入参数而已——keep it simple,就用JDK的方法就好了。如果你要解析大量的消息,又必须调用一个类似pack这样的方法——那确实得去优化一下了。

新的pack方法和旧的实现差不太多——把一个字符串转化成一个尽可能小的Character/Integer/Long/Double/String类型,使得result.toString().equals(orginalString)为true。

[java]
  1. <br>public static Object strToObject( final String str )  
  2. <br>{  
  3. <br>    if ( str == null || str.length() > 17 )  
  4. <br>    {  //out of Long range  
  5. <br>        return str;  
  6. <br>    }  
  7. <br>    if ( str.equals( "" ) )  
  8. <br>        return ""//ensure interned string is returned  
  9. <br>    if ( str.length() == 1 )  
  10. <br>        return str.charAt( 0 ); //return Character  
  11. <br>    //if starts with zero - support only "0" and "0.something"  
  12. <br>    if ( str.charAt( 0 ) == '0' )  
  13. <br>    {  
  14. <br>        if ( str.equals( "0" ) )  
  15. <br>            return 0;  
  16. <br>        if ( !str.startsWith( "0." ) )  //this may be a double  
  17. <br>            return str;  
  18. <br>    }  
  19. <br>   
  20. <br>    long res = 0;  
  21. <br>    int sign = 1;  
  22. <br>    for ( int i = 0; i < str.length(); ++i )  
  23. <br>    {  
  24. <br>        final char c = str.charAt( i );  
  25. <br>        if ( c <= '9' && c >= '0' )  
  26. <br>            res = res * 10 + ( c - '0' );  
  27. <br>        else if ( c == '.' )  
  28. <br>        {  
  29. <br>            //too lazy to write a proper Double parser, use JDK one  
  30. <br>            try  
  31. <br>            {  
  32. <br>                final Double val = Double.valueOf( str );  
  33. <br>                //check if value converted back to string equals to an original string  
  34. <br>                final String reverted = val.toString();  
  35. <br>                return reverted.equals( str ) ? val : str;  
  36. <br>            }  
  37. <br>            catch ( NumberFormatException ex )  
  38. <br>            {  
  39. <br>                return str;  
  40. <br>            }  
  41. <br>        }  
  42. <br>        else if ( c == '-' )  
  43. <br>        {  
  44. <br>            if ( i == 0 )  
  45. <br>                sign = -1//switch sign at first position  
  46. <br>            else  
  47. <br>                return str; //otherwise it is not numeric  
  48. <br>        }  
  49. <br>        else if ( c == '+' )  
  50. <br>        {  
  51. <br>            if ( i == 0 )  
  52. <br>                sign = 1//sign at first position  
  53. <br>            else  
  54. <br>                return str; //otherwise it is not numeric  
  55. <br>        }  
  56. <br>        else //non-numeric  
  57. <br>            return str;  
  58. <br>    }  
  59. <br>    //cast to int if value is in int range  
  60. <br>    if ( res < Integer.MAX_VALUE )  
  61. <br>        return ( int ) res * sign;  
  62. <br>    //otherwise return Long  
  63. <br>    return res * sign;  
  64. <br>}  
  65. <br>  



很惊讶吧,新的方法解析数字比JDK的实现快多了!很大一个原因是因为JDK在解析的最后,调用了一个支持的解析方法,像这样:

public static int parseInt( String s, int radix ) throws NumberFormatException


新的方法和旧的相比(注意方法调用的次数——对于非数字串pack只调用了1百万次,而别的情况能调用到千万级别):

[java]

  1. <br>Pack: Made 100.000.000 iterations for string '12345' : time = 12.145 sec  
  2. <br>Pack: Made 1.000.000 iterations for string '12345a' : time = 23.248 sec  
  3. <br>strToObject: Made 100.000.000 iterations for string '12345' : time = 6.311 sec  
  4. <br>strToObject: Made 100.000.000 iterations for string '12345a' : time = 5.807 sec  
  5. <br>  




总结



千万不要把异常当成返回码一样用,或者当作可能发生的事件(尤其是和IO无关的方法)往外抛。抛异常的代价太昂贵了,对于一般的方法,至少要慢百倍以上。

如果你每条数据都需要解析,又经常会出现非数值串的时候,尽量不要用Number子类型的parse*/valueOf这些方法。为了性能考虑,你应当手动解析它们。


本文固定链接: http://www.devba.com/index.php/archives/5332.html | 开发吧

报歉!评论已关闭.