Jak rychlý je čas (v Javě)?
Práce s datem a časem mi v Javě 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í:
- Znovupoužití objektů
GregorianCalendar,SimpleDateFormataDateTimeFormatterje 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). - Parsování stringu zabere ~300 ns času, víc než vytvoření instance
DateneboLocalDate LocalDateje 30× rychlejší než staré API. Za to může fakt, žeLocalDateje trojice čísel den, měsíc, rok, kdežtoDatereprezentuje č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í. ObjektuLocalDatepř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:
- 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.LocalDateTimemusí timestamp vždy spočítat, což není úplně triviální. - Na druhou stranu, porovnání dvou
LocalDateTimeobjektů je stále velice rychlé.