Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 380|回复: 0

Java异常的性能分析

[复制链接]
  • TA的每日心情
    奋斗
    3 天前
  • 签到天数: 757 天

    [LV.10]以坛为家III

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    707886
    发表于 2021-7-20 17:32:25 | 显示全部楼层 |阅读模式

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt276

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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"需要多长的时间:

    1
    2
    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倍。大多数的处理时间都在填充异常的栈跟踪信息了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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
    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百万次,而别的情况能调用到千万级别):

    1
    2
    3
    4
    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这些方法。为了性能考虑,你应当手动解析它们。

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-7-5 02:07 , Processed in 0.060254 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表