0xDEADBEEF

[RSS]
««« »»»

Jak rychlý je čas (v Javě)?

13. 3. 2019

Práce s datem a časem mi přišla vždycky poněkud pomalá. Při parsování dat často hodně času připadlo na zpracování datumů.

V Javě máme dvě možnosti jak na data: Jednak historický artefakt java.util.Date a pak novější java.time.LocalDateTime, které není zas tak nové, když vezmeme v potaz, že si odbylo svou premiéru už v Javě 8.

Otázka zní: Jak rychle můžeme vytvořit instanci každého z nich?

Abych to zodpověděl, napsal jsem malý JMH benchmark, který testuje výrobu Date objektů přes GregorianCalendar nebo s pomocí SimpleDateFormat a výrobu objektů LocalDate přes DateTimeFormatter nebo přímo statickou metodou of.

def calendar_notReused: Date =
  new GregorianCalendar(2018, 11, 25).getTime

def calendar_reused: Date = {
  cal.set(Calendar.YEAR, 2018)
  cal.set(Calendar.MONTH, 11)
  cal.set(Calendar.DAY_OF_MONTH, 25)
  cal.getTime
}

def simpleDateFormat_notReused: Date =
  new SimpleDateFormat("yyyy-MM-dd").parse(str)

def simpleDateFormat_reused: Date =
  simpleDateFormat.parse(str)

def localDateTimeParse_notReused: LocalDate =
  LocalDate.parse(str, DateTimeFormatter.ofPattern("yyyy-MM-dd"))

def localDateTimeParse_reused: LocalDate =
  LocalDate.parse(str, dateTimeFormatter)

def localDateTime: LocalDate =
  LocalDate.of(2018, 11, 25)

Výsledky jsou následující.

Benchmark                               Mode  Cnt     Score    Error  Units
Calendars.calendar_notReused            avgt    4   206,018 ±  3,370  ns/op
Calendars.calendar_reused               avgt    4   144,375 ±  0,422  ns/op
Calendars.simpleDateFormat_notReused    avgt    4  1396,633 ±  2,591  ns/op
Calendars.simpleDateFormat_reused       avgt    4   587,046 ± 15,300  ns/op
Calendars.localDateTimeParse_notReused  avgt    4   534,103 ± 24,506  ns/op
Calendars.localDateTimeParse_reused     avgt    4   346,680 ±  4,762  ns/op
Calendars.localDateTime                 avgt    4     5,771 ±  0,079  ns/op

Je z nich vidět několik věcí:

  1. Znovupoužití objektů GregorianCalendar, SimpleDateFormatDateTimeFormatter je rychlejší než jejich opakované vytváření. Někdy o čtvrtinu, jindy víc než dvakrát. (To není překvapivé, z opakovaného použití benefituje například i XMLStreamWriter nebo regex Matcher).
  2. Parsování stringu zabere ~300 ns času, víc než vytvoření instance Date nebo LocalDate
  3. LocalDate je 30x rychlejší než staré API. Za to může fakt, že LocalDate je trojice čísel den, měsíc, rok, kdežto Date reprezentuje čas jako konkrétní GMT okamžik ve formě počtu milisekund. Proto musí při vytváření brát v potaz časové zóny a musí projít logikou gregoriánského kalendáře, která není úplně triviální. Objektu LocalDate při vytvoření předám tři čísla, on je nastaví do vnitřních proměnných a má hotovo. Neví nic o časových zónách a nemusí počítat vzdálenost od určitého referenčního momentu.

Navíc je nové java.time API immutable a bezpečné při použití ve více vláknech. Není tedy nutné se rozhodovat mezi rychlostí a machinacemi s ThreadLocal proměnnými.

Dodatky:

  1. Pokud potřebujete pracovat s daty jako timestampy, protože například musíte počítat kolik uběhlo mezi dvěma okamžiky vteřin/minut/hodin/dnů, nové API na to má třídu java.time.Instant. LocalDateTime musí timestamp vždy spočítat, což není úplně triviální.
  2. Na druhou stranu, porovnání dvou LocalDateTime objektů je stále velice rychlé.
píše k47 (@kaja47, k47)