Java异常的性能分析(一)

2014-11-24 00:41:30 · 作者: · 浏览: 2



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

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


at java.lang.Throwable.fillInStackTrace(Throwable.java:-1)
at java.lang.Throwable.fillInStackTrace(Throwable.java:782)
- locked <0x6c> (a sun.misc.CEStreamExhausted)
at java.lang.Throwable. (Throwable.java:250)
at java.lang.Exception. (Exception.java:54)
at java.io.IOException. (IOException.java:47)
at sun.misc.CEStreamExhausted. (CEStreamExhausted.java:30)
at sun.misc.BASE64Decoder.decodeAtom(BASE64Decoder.java:117)
at sun.misc.CharacterDecoder.decodeBuffer(CharacterDecoder.java:163)
at sun.misc.CharacterDecoder.decodeBuffer(CharacterDecoder.java:194)



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


Made 100.000.000 iterations for string '12345' : time = 12.109 sec
Made 1.000.000 iterations for string '12345a' : time = 21.764 sec



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



at java.lang.Throwable.fillInStackTrace(Throwable.java:-1)
at java.lang.Throwable.fillInStackTrace(Throwable.java:782)
- locked <0x87> (a java.lang.NumberFormatException)
at java.lang.Throwable. (Throwable.java:265)
at java.lang.Exception. (Exception.java:66)
at java.lang.RuntimeException. (RuntimeException.java:62)
at java.lang.IllegalArgumentException. (IllegalArgumentException.java:53)
at java.lang.NumberFormatException. (NumberFormatException.java:55)
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:441)
at java.lang.Long.valueOf(Long.java:540)
at com.mvorontsov.javaperf.StrConvTests.pack(StrConvTests.java:69)
at com.mvorontsov.javaperf.StrConvTests.test(StrConvTests.java:38)
at com.mvorontsov.javaperf.StrConvTests.main(StrConvTests.java:29)



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

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


public static Object strToObject( final String str )
{
if ( str == null || str.length() > 17 )
{ //out of Long range
return str;
}
if ( str.equals( "" ) )
return ""; //ensure interned string is returned
if ( str.length() == 1 )
return str.charAt( 0 ); //return Character
//if starts with zero - support only "0" and "0.something"
if ( str.charAt( 0 ) == '0' )
{
if ( str.equals( "0" ) )
return 0;
if ( !str.startsWith( "0." ) ) //this may be a double
return str;
}

long res = 0;
int sign = 1;
for ( int i = 0; i < str.length(); ++i )
{
final char c = str.charAt( i );
if ( c <= '9' && c >= '0' )
res = res * 10 + ( c - '0' );
else if ( c == '.' )
{
//too lazy to write a proper Double parser, use JDK one
try
{
final Double val = Double.valueOf( str );
//check if value converted back to string equals to an original string
final String reverted = val.toString();
return reverted.equals( str ) val : str;
}
catch ( NumberFormatException ex )
{
return str;
}
}
else if ( c == '-' )
{
if ( i == 0 )
sign = -1; //switch sign at first position
else
return str; //otherwise i