2011-09-12 6 views
3

私は、ミリ秒単位で日付フォーマッタのJava実装を探しています。 SimpleDateFormatterはGCにガベージを生成するので、使用しないでください。私は高速でガーベッジフリーのJava実装を探しています。誰かがそれをどこかに見ましたか?現在のミリ秒の書式設定文字列なしSimpleDateFormatter(ガベージを生成する)

StringBuilder sb = getReusableStringBuilder(); 

parse(sb, System.currentTimeMillis()); 

EDIT:これはロギングライブラリのため、時間も含める必要があります。

+3

ガベージを生成したくない場合は、ガベージコレクション言語を使用しないでください。なぜあなたはSimpleDateFormatによって生成されたゴミを気にしていますか? –

+0

@JB Nizet:私はこのオプションを持っていません。 Javaを使用する必要があり、GCの待ち時間を最小限に抑える必要があります。これは、低レイテンシの業界にとって珍しい要件ではありません。 – TraderJoeChicago

+0

@JB Nizet、あなたのアプリケーションの1%未満がGCレスである必要がある場合、この要件に合うように他の99%を変更しますか? –

答えて

-1

解決策は思ったよりはるかに簡単でした。つまり、メモリをリークしないようにFieldPositionをブートストラップします。

+0

フォーマットロジックを実装するには苦労します。 – TraderJoeChicago

+0

誰もフォーマットロジックを実装したコードに私を誘導してくれなかったので、他に何も出てこない場合は、この回答を選択します。 – TraderJoeChicago

3

ここには、バックグラウンドでログするための時間/日付とStringBuilderを記録する背景ロガーがあります。典型的な待ち時間は1コールあたり1マイクロ秒未満です。 GCは何も生産されないようにすべてをリサイクルします。

これは、キューを使用して2つのスレッド間で作業を渡すよりもはるかに効率的です。残念ながら、すべてのキュー実装ではガベージが作成されます。 :(

import java.util.concurrent.Exchanger; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 

class BackgroundLogger implements Runnable { 
    static final int ENTRIES = 64; 

    static class LogEntry { 
    long time; 
    int level; 
    final StringBuilder text = new StringBuilder(); 
    } 

    static class LogEntries { 
    final LogEntry[] lines = new LogEntry[ENTRIES]; 
    int used = 0; 
    } 

    private final ExecutorService executor = Executors.newSingleThreadExecutor(); 
    final Exchanger<LogEntries> logEntriesExchanger = new Exchanger<LogEntries>(); 
    LogEntries entries = new LogEntries(); 

    BackgroundLogger() { 
    executor.submit(this); 
    } 

    // put whatever you want in the StringBuilder, before the next call! 
    public StringBuilder log(int level) { 
    try { 
     if (entries.used == ENTRIES) 
     entries = logEntriesExchanger.exchange(entries); 
     LogEntry le = entries.lines[entries.used++]; 
     le.time = System.currentTimeMillis(); 
     le.level = level; 
     return le.text; 

    } catch (InterruptedException e) { 
     throw new RuntimeException(e); 
    } 
    } 

    public void flush() throws InterruptedException { 
    entries = logEntriesExchanger.exchange(entries); 
    entries = logEntriesExchanger.exchange(entries); 
    } 

    public void stop() { 
    try { 
     flush(); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 
    executor.shutdownNow(); 
    } 

    @Override 
    public void run() { 
    LogEntries entries = new LogEntries(); 
    try { 
     while(!Thread.interrupted()) { 
     entries = logEntriesExchanger.exchange(entries); 
     for (int i = 0; i < entries.used; i++) { 
      bgLog(entries.lines[i]); 
      entries.lines[i].text.delete(0, entries.lines[i].text.length()); 
     } 
     entries.used = 0; 
     } 
    } catch (InterruptedException ignored) { 

    } finally { 
     System.out.println("logger stopping."); 
    } 
    } 

    private void bgLog(LogEntry line) { 
    // log the entry to a file. 
    } 
} 

私が1を書かれている。

あなたがゴミの少しを許可する場合は、あなたの問題を簡素化することができます。あなたは、日付にそれが(つまり、一度変更するたびにフォーマットするSimpleDateFormatterを使用することができます

注:文字列を作成することによって、ごみ(StringBuilderを使用しない場合でも、文字列とchar [])が生成されます。

GCを避けるために、リサイクルされたByteBufferに追加します。 (夜中頃を除く)

@Joachim Saucerは、フォーマッタを使用してゴミを減らすことができると示唆しています。私はあなたがStringの制作を落とさないと、大きな違いは生じないと思う。

SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS"); 
StringBuffer sb = new StringBuffer(); 
Date tmpDate = new Date(); 
final FieldPosition pos = new FieldPosition(0); 
{ 
    long free1 = Runtime.getRuntime().freeMemory(); 
    for (int i = 0; i < 1000; i++) { 
    tmpDate.setTime(System.currentTimeMillis()); 
    sdf.format(tmpDate, sb, pos); 
    sb.delete(0, sb.length()); 
    } 
    long free2 = Runtime.getRuntime().freeMemory(); 
    if (free1 == free2) throw new Error("This must be run with -XX:-UseTLAB"); 
    System.out.println("SDF.format used an average of " + (free1 - free2)/1000 + " bytes"); 
} 
{ 
    long free1 = Runtime.getRuntime().freeMemory(); 
    for (int i = 0; i < 1000; i++) { 
    tmpDate.setTime(System.currentTimeMillis()); 
    sdf.format(tmpDate, sb, pos); 
    String str = sb.toString(); 
    sb.delete(0, sb.length()); 
    } 
    long free2 = Runtime.getRuntime().freeMemory(); 
    if (free1 == free2) throw new Error("This must be run with -XX:-UseTLAB"); 
    System.out.println("SDF.format with a String used an average of " + (free1 - free2)/1000 + " bytes"); 
} 

プリントあなたは何の日付と時刻の関連を処理するために必要がある場合

SDF.format used an average of 24 bytes 
SDF.format with a String used an average of 120 bytes 
+0

StringBuilderを使用してStringを生成しないので、書式設定ロジックはStringBuilderに書き込みます。私はそれを明確にするために私の質問を変えています。 – TraderJoeChicago

+1

あなたのアプローチはクールです、ピーター。私は日付をキャッシュし、各ログの時間だけを計算しても構いません。しかし、時間ロジックは、特に、昼間の節約、タイムゾーンなどで苦痛になる可能性があります。自分で何かをコーディングする前に、すでに何かが見つかったかどうかを確認してください。 – TraderJoeChicago

+0

'FieldPosition'を再利用することで、平均メモリ使用量をわずか24バイトに減らすことができます(さらに32バイト削減!)。 'SimpleDateFormat'と' StringBuffer'をすでに共有しているので、おそらく保存されているので、何らかの方法で同期させる必要があります。 –

-3

常にJoda Timeを使用しています。フォーマッタも含まれています。

+4

おそらく、それは最小限のGCを生成するようにそれを使用する方法を説明することができます。 –

0

この目的のために、DateTimesユーティリティクラスを使用してライブラリを作成しました。内部的には、1970/01/01 00:00:00.000から「ミリ秒」の「長い値」をとり、年、月、日、時、分、秒、ミリ秒の値を計算します。次に、この情報をASCII文字列として、指定されたバイト配列に格納します。GC用の新しいオブジェクトはありません。このバイト配列は、新しいStringオブジェクトを作成せずにSystem.out.write()メソッドを使用してコンソールに出力できます。

私のウェブサイトhereからjarファイルとしてライブラリを入手できます。記事では、使用方法を説明し、パフォーマンスを比較します。

関連する問題