Java異常のパフォーマンス分析


Javaで異常を投げつける性能は非常に悪い.通常、異常を投げると100~1000クロックのビートが消費されます.
通常は思いがけない間違いが発生して、私たちは異常を外に投げます.つまり、1つのプロセスが1秒で何千もの異常を投げ出すことを望んでいないに違いありません.しかし、時には異常を事件のように投げ出す方法があります.僕らは
この文章には、sun.misc.BASE 64 Decoderがパフォーマンスが悪いのは、異常を投げて「もっとデータが必要だ」と要求しているからです.

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」と「12345 a」を梱包するのにどれくらいの時間がかかるかを見てみましょう.

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

「12345 a」反復の回数は「12345」より100倍少ないことがわかる.すなわち,この方法は「12345 a」の処理が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法の性能を容易に向上させることができる.しかし、忘れないでください.やむを得ず、勝手に最適化しないでください.いくつかの入力パラメータを解析するだけなら、JDKの方法でいいです.大量のメッセージを解析するには、packのような方法を呼び出さなければなりません.それは確かに最適化しなければなりません.
新しいpack方法と古い実現はあまり差がない--1つの文字列をできるだけ小さい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などの方法は使用しないでください.パフォーマンスを考慮するには、手動で解析する必要があります.
オリジナル記事の転載は出典を明記してください.
http://it.deepinmind.com
英語のテキストリンク