2014-02-28 6 views
19

Dies ist auf Java 7 (51) auf RHEL mit 24 Kernen Wir bemerken eine Erhöhung der durchschnittlichen Antwortzeiten eines Java-SimpleDateFormat in lokalen Thread eingewickelt, wie wir die Thread-Pool-Größe erhöhen. Wird das erwartet? oder mache ich einfach etwas Dummes?Wie verbessert man die Leistung von SimpleDateFormat in ThreadLocal eingewickelt?

enter image description here

Testprogramm

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 Klasse:

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; 
     } 
    } 
} 

Jemand schlug vor, so die Atomic nehmen wollte nur teilen, dass es keine Rolle spielt die bei der Steigerung durchschnittliche Zeit:

##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 
+0

Instanz des lokalen Threads gibt diese Instanz zurück, um Operationen für Daten auszuführen, wenn dies keine leere Instanz ist, oder eine neue Instanz bei Bedarf initialisieren. –

+0

Können Sie SimpleDateFormat sdf als private statische Variable zu ISODateFormatter hinzufügen und im statischen Block initialisieren? Dann würden Sie jedes Mal, wenn initialValue() aufgerufen wird, dieselbe Instanz zurückgeben. – Boris

+0

@ user3301492: SimpleDateFormat ist nicht threadsicher, also wäre das eine schlechte Idee. – beny23

Antwort

5

Erstellen einer Instanz von SimpleDateFormat ist sehr teuer (this article zeigt einige Profiling/Benchmarking). Wenn dies wahr ist, verglichen mit dem Parsen der Daten in Zeichenfolgen, folgt daraus, dass die durchschnittliche Zeit zunimmt, wenn Sie die Anzahl der Threads (und damit die Anzahl der SimpleDateFormat-Instanzen, da sie Threadlocals sind) erhöhen.

+0

Blog-Link ist kaputt. – Koekiebox

6

SimpleDateFormat nicht Thread-sicher

Da die correct answer by Martin Wilson Staaten, ein Simple Instanziieren relativ teuer ist.

Wissen, dass Ihr erster Gedanke sein könnte, "Nun, lassen Sie uns eine Instanz für die Wiederverwendung zwischenspeichern.". Netter Gedanke, aber Vorsicht: Die SimpleDateFormat-Klasse in nicht Thread-sicher. So sagt the class documentation unter seiner Synchronisation Überschrift.

Joda-Time

Eine bessere Lösung ist es, die notorisch lästig (und jetzt veraltet) java.util.Date, .Calendar und Simple Klassen zu vermeiden. Stattdessen verwenden Sie entweder:

  • Joda-Time
    Dritt Open-Source-Bibliothek, beliebten Ersatz für Datum/Kalender.
  • java.time package
    Neu, gebündelt in Java 8, die alten Datum/Kalender Klassen verdrängend, inspiriert von Joda-Time, definiert durch JSR 310.

Joda-Time ist absichtlich zu sein Thread-sicher gebaut, vor allem durch Verwendung von . Es gibt einige veränderbare Klassen, aber diese werden normalerweise nicht verwendet.

Diese other question on StackOverflow erklärt, dass die Klasse tatsächlich Thread-sicher ist. Sie können also eine Instanz erstellen, sie zwischenspeichern und alle Ihre Threads verwenden, ohne zusätzliche Synchronisations- oder andere Steuerelemente für den gemeinsamen Zugriff hinzuzufügen.

+1

Das OP speichert ein SimpleDateFormat in einem ThreadLocal. Jeder Thread hat somit seine eigene SimpleDateFormat-Instanz, die nicht mit anderen Threads geteilt wird. Das ist also eine gute Möglichkeit, ein SimpleDateFormat wiederzuverwenden, ohne Threadsicherheitsprobleme zu riskieren. –

4

Ein anderer Ansatz zur Beschleunigung Ihrer Formatierung besteht darin, das formatierte Ergebnis zwischenzuspeichern. Dies berücksichtigt die Tatsache, dass es in der Regel nicht so viele unterschiedliche Datumsangaben gibt. Wenn Sie die Formatierung von Datum und Uhrzeit teilen, ist dies sogar ein besserer Kandidat für das Caching.

Der Nachteil davon ist, dass normale Java-Cache-Implementierungen, wie EHCache, zu langsam sind, der Cache-Zugriff dauert nur länger als die Formatierung.

Es gibt eine weitere Cache-Implementierung, die Zugriffszeiten auf Augenhöhe mit einer HashMap bietet. In diesem Fall bekommst du eine schöne Beschleunigung. Hier finden Sie meine Proof of Concept Tests: https://github.com/headissue/cache2k-benchmark/blob/master/zoo/src/test/java/org/cache2k/benchmark/DateFormattingBenchmark.java

Vielleicht kann dies eine Lösung in Ihrem Szenario sein.

Haftungsausschluss: Ich arbeite an cache2k ....

0

Unsere usecase einmal war schreiben (Single-Thread) und viele Male (gleichzeitig) lesen. Also konvertierte ich Datum in String zum Zeitpunkt der Speicherung der Daten, anstatt jedes Mal, wenn eine Anfrage beantwortet werden muss.

Verwandte Themen