Java异常的性能分析

标签: java 异常 性能分析 | 发表时间:2014-03-25 16:25 | 作者:
出处:http://it.deepinmind.com

在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.<init>(Throwable.java:250)
at java.lang.Exception.<init>(Exception.java:54)
at java.io.IOException.<init>(IOException.java:47)
at sun.misc.CEStreamExhausted.<init>(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.<init>(Throwable.java:265)
        at java.lang.Exception.<init>(Exception.java:66)
        at java.lang.RuntimeException.<init>(RuntimeException.java:62)
        at java.lang.IllegalArgumentException.<init>(IllegalArgumentException.java:53)
        at java.lang.NumberFormatException.<init>(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 it is not numeric
        }
        else if ( c == '+' )
        {
            if ( i == 0 )
                sign = 1; //sign at first position
            else
                return str; //otherwise it is not numeric
        }
        else //non-numeric
            return str;
    }
    //cast to int if value is in int range
    if ( res < Integer.MAX_VALUE )
        return ( int ) res * sign;
    //otherwise return Long
    return res * sign;
}

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

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

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

   Pack: Made 100.000.000 iterations for string '12345' : time = 12.145 sec
Pack: Made 1.000.000 iterations for string '12345a' : time = 23.248 sec
strToObject: Made 100.000.000 iterations for string '12345' : time = 6.311 sec
strToObject: Made 100.000.000 iterations for string '12345a' : time = 5.807 sec

总结

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

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

原创文章转载请注明出处: Java异常的性能分析

英文原文链接

相关 [java 异常 性能分析] 推荐:

Java异常的性能分析

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

Java可变参数的性能分析

- - Java译站
可变长参数列表是Java 5中的一个新特性. 如果方法需要传入多个同类型参数的话,这个功能就非常有用. 比如说,Java 5之前如果要写一个方法来将所有入参打印到控制台上的话,它的代码会是这样的:. Java 5增加了对可变参数的支持. 这个方法现在看起来就简单多了(译注:这里看起来简单难道不是因为新的for循环.

Java异常

- - CSDN博客推荐文章
“好的程序设计语言能够帮助程序员写出好程序,但是无论哪种语言都避免不了程序员写出坏的程序.                                                                                                                          ----《Java编程思想》.

浅谈java异常

- - 移动开发 - ITeye博客
在《java编程思想》中这样定义 异常:阻止当前方法或作用域继续执行的问题. 虽然java中有异常处理机制,但是要明确一点,决不应该用"正常"的态度来看待异常. 绝对一点说异常就是某种意义上的错误,就是问题,它可能会导致程序失败. 之所以java要提出异常处理机制,就是要告诉开发人员,你的程序出现了不正常的情况,请注意.

Java 8并行操作的性能分析

- - Java译站
当我在写这篇博客的时候,我还在为昨天聚会上说过的话感到尴尬,当时大家看我跟看怪物一样. 好吧,不过所幸的是我并不孤单——Java 8它也很擅长这口. Java 8中一个关键的新特性就是它支持并行数组操作. 你可以使用lambda表达式来进行排序,过滤,分组等操作,它能自动的发挥多核架构的优势. 带来的好处就是作为一名Java开发人员,你只需很小的工作量就可以立马获得性能的提升.

Java异常处理策略

- - 研发管理 - ITeye博客
任务与预先设定的规则不相符的情况都可以称之为异常. 但凡业务逻辑操作,都会划定一些边界或规则,但是往往事与愿违,总会有调皮鬼来挑战系统的健壮性. 用户并不都是知道潜规则的,比如用户的银行账户中只有100块钱,但是用户不查询就直接取200块. 码农有时候太过自信了,比如你在编写文件下载功能时忽略了文件有可能不存在这个分支流程.

Java异常处理的陋习展播

- - 博客 - 伯乐在线
注:本文来自志军(@ _Zhijun )在微博推荐的一篇转载于2007年09月27的旧文,他说“没法找到原作者”. 的确,我也花了半个多小时在找原作者. 先是找到了一个标注“2005年6月18日转字Java研究组织”的文章(“Java研究组织”的域名已过期),后来找到是大连理工大学碧海青天BBS上一个发于的2003年5月中旬的 合集帖子,其中提到一条来自 CSDN 的链接,可惜该链接已挂,否则应该能找到作者的.

Java Socket常见异常处理

- - BlogJava-qileilove
java网络编程Socket通信中,通常会遇到以下异常情况:.   第1个异常是 java.net.BindException:Address already in use: JVM_Bind.   该异常发生在服务器端进行new ServerSocket(port)(port是一个0,65536的整型值)操作时.

捕获Java线程池执行任务抛出的异常

- - BlogJava-首页技术区
Java中线程执行的任务接口java.lang.Runnable 要求不抛出Checked异常,. 那么如果 run() 方法中抛出了RuntimeException,将会怎么处理了. 通常java.lang.Thread对象运行设置一个默认的异常处理方法:. 而这个默认的静态全局的异常捕获方法时输出堆栈.

Java EE项目中异常设计及处理总结

- - Java - 编程语言 - ITeye博客
异常,为我们处理非正常的业务流程提供了很好的解决方案,如果你有过dbase、c、pascal等过程式语言开发的经历,你一定会深刻体会到,异常机制给你的代码可读行、可维护性带来的好处,同时,程序的健壮性也得到了增强. 在 java项目中,异常设计要注意下面的几点. A、自定义异常的父类,可以选择为RuntimeException或Exception.