web-dev-qa-db-ja.com

ThreadLocalでラップされたSimpleDateFormatのパフォーマンスを改善する方法は?

これは、24コアのRHELでJava 7(51)にあります。Java SimpleDateFormatがスレッドローカルにラップされているためスレッドプールのサイズを増やします。これは予想されることですか?

enter image description here

テストプログラム

    public class DateFormatterLoadTest {
        private static final Logger LOG = Logger.getLogger(DateFormatterLoadTest .class);
        private final static int CONCURRENCY = 10;

        public static void main(String[] args) throws Exception {
            final AtomicLong total = new AtomicLong(0);
            ExecutorService es = Executors.newFixedThreadPool(CONCURRENCY);
            final CountDownLatch cdl = new CountDownLatch(CONCURRENCY);
            for (int i = 0; i < CONCURRENCY; i++) {
                es.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            int size = 65000;
                            Date d = new Date();

                            long time = System.currentTimeMillis();
                            for (int i = 0; i < size; i++) {
                                String sd = ISODateFormatter.convertDateToString(d);
                                assert (sd != null);
                            }
                            total.addAndGet((System.currentTimeMillis() - time));

                        } catch (Throwable t) {
                            t.printStackTrace();
                        } finally {
                            cdl.countDown();
                        }
                    }
                });
            }
            cdl.await();
            es.shutdown();
            LOG.info("TOTAL TIME:" + total.get());
            LOG.info("AVERAGE TIME:" + (total.get() / CONCURRENCY));
        }
    }

DateFormatterクラス:

public class ISODateFormatter {
    private static final Logger LOG = Logger.getLogger(ISODateFormatter.class);

    private static ThreadLocal<DateFormat> dfWithTZ = new ThreadLocal<DateFormat>() {
        @Override
        public DateFormat get() {
            return super.get();
        }

        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",
                    Locale.ENGLISH);
        }

        @Override
        public void remove() {
            super.remove();
        }

        @Override
        public void set(DateFormat value) {
            super.set(value);
        }

    };

    public static String convertDateToString(Date date) {
        if (date == null) {
            return null;
        }
        try {
            return dfWithTZ.get().format(date);
        } catch (Exception e) {
            LOG.error("!!! Error parsing dateString: " + date, e);
            return null;
        }
    }
}

誰かがAtomicLongを削除することを提案したので、平均時間を増やすのに何の役割も果たしていないことを共有したかっただけです。

##NOT USING ATOMIC LONG##
2014-02-28 11:03:52,790 [pool-1-thread-1] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:328
2014-02-28 11:03:52,868 [pool-1-thread-6] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:406
2014-02-28 11:03:52,821 [pool-1-thread-2] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:359
2014-02-28 11:03:52,821 [pool-1-thread-8] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:359
2014-02-28 11:03:52,868 [pool-1-thread-4] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:406
2014-02-28 11:03:52,915 [pool-1-thread-5] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:453
2014-02-28 11:03:52,930 [pool-1-thread-7] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:468
2014-02-28 11:03:52,930 [pool-1-thread-3] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:468
2014-02-28 11:03:52,930 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - CONCURRENCY:8

##USING ATOMIC LONG##
2014-02-28 11:02:53,852 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - TOTAL TIME:2726
2014-02-28 11:02:53,852 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - CONCURRENCY:8
2014-02-28 11:02:53,852 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - AVERAGE TIME:340

##NOT USING ATOMIC LONG##
2014-02-28 11:06:57,980 [pool-1-thread-3] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:312
2014-02-28 11:06:58,339 [pool-1-thread-8] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:671
2014-02-28 11:06:58,339 [pool-1-thread-4] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:671
2014-02-28 11:06:58,307 [pool-1-thread-7] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:639
2014-02-28 11:06:58,261 [pool-1-thread-6] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:593
2014-02-28 11:06:58,105 [pool-1-thread-15] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:437
2014-02-28 11:06:58,089 [pool-1-thread-13] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:421
2014-02-28 11:06:58,073 [pool-1-thread-1] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:405
2014-02-28 11:06:58,073 [pool-1-thread-12] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:405
2014-02-28 11:06:58,042 [pool-1-thread-14] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:374
2014-02-28 11:06:57,995 [pool-1-thread-2] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:327
2014-02-28 11:06:57,995 [pool-1-thread-16] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:327
2014-02-28 11:06:58,385 [pool-1-thread-10] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:717
2014-02-28 11:06:58,385 [pool-1-thread-11] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:717
2014-02-28 11:06:58,417 [pool-1-thread-9] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:749
2014-02-28 11:06:58,418 [pool-1-thread-5] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:750
2014-02-28 11:06:58,418 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - CONCURRENCY:16

##USING ATOMIC LONG##
2014-02-28 11:07:57,510 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - TOTAL TIME:9365
2014-02-28 11:07:57,510 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - CONCURRENCY:16
2014-02-28 11:07:57,510 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - AVERAGE TIME:585
21
Debajyoti Roy

SimpleDateFormatスレッドセーフではありません

Martin Wilsonによる正解 が述べているように、SimpleDateFormatのインスタンス化は比較的コストがかかります。

あなたの最初の考えが「まあ、再利用のためにインスタンスをキャッシュしましょう」かもしれないことを知っています。良い考えですが、注意してください:のSimpleDateFormatクラスはスレッドセーフではありません。つまり、 クラスのドキュメントSynchronizationの見出しの下にあります。

ジョーダタイム

より良い解決策は、悪名高い厄介な(そして現在は時代遅れの)Java.util.Date、.Calendar、およびSimpleDateFormatクラスを回避することです。代わりに、次のいずれかを使用してください。

  • Joda-Time
    サードパーティのオープンソースライブラリ、Date/Calendarの人気のある代替品。
  • Java.timeパッケージ
    新しい、 Java 8 にバンドルされ、JSR310で定義されたJoda-Timeに触発された古いDate/Calendarクラスに取って代わります。

Joda-Timeは、主に 不変オブジェクト を使用して、スレッドセーフになるように意図的に構築されています。いくつかの可変クラスがありますが、それらは通常使用されません。

この StackOverflowに関する他の質問 は、DateTimeFormatterクラスが実際にスレッドセーフであることを説明しています。したがって、1つのインスタンスを作成してキャッシュし、同期やその他の同時実行制御を追加することなく、すべてのスレッドにそのフォーマッターを使用させることができます。

7
Basil Bourque

SimpleDateFormatのインスタンスの作成は非常にコストがかかります( この記事 はいくつかのプロファイリング/ベンチマークを示しています)。これが当てはまる場合、日付を文字列に解析する場合と比較して、スレッドの数(したがって、スレッドローカルであるSimpleDateFormatインスタンスの数)を増やすと、平均時間が長くなります。

6
Martin Wilson

フォーマットを高速化する別のアプローチは、フォーマットされた結果をキャッシュすることです。これは、通常、フォーマットする日付がそれほど多くないという事実を考慮しています。日付と時刻のフォーマットを分割する場合は、キャッシュの候補としてさらに適しています。

これの欠点は、通常のJava EHCacheのようなキャッシュ実装は遅くなり、キャッシュアクセスはフォーマットよりも時間がかかることです。

HashMapと同等のアクセス時間を持っている別のキャッシュ実装があります。この場合、あなたは素晴らしいスピードアップを得ます。ここに私の概念実証テストがあります: https://github.com/headissue/cache2k-benchmark/blob/master/Zoo/src/test/Java/org/cache2k/benchmark/DateFormattingBenchmark.Java ==

たぶん、これはあなたのシナリオ内の解決策になる可能性があります。

免責事項:私は取り組んでいます cache2k ..。

5
cruftex

私たちのユースケースは、1回の書き込み(シングルスレッド)と複数回の読み取り(同時)でした。そのため、リクエストに応答する必要があるたびにこれを行うのではなく、データの保存時に日付を文字列に変換しました。

0
Debajyoti Roy