0xDEADBEEF

[RSS]
««« »»»

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

13. 3. 2019

Práce s datem a časem mi v Javě přišla vždycky po­ně­kud pomalá. Při par­so­vání dat často hodně času při­padlo na zpra­co­vání datumů.

V Javě máme dvě mož­nosti jak na data: Jednak his­to­rický ar­te­fakt java.util.Date a pak no­vější java.time.LocalDateTime, které není zas tak nové, když vez­meme v potaz, že si odbylo svou pre­mi­éru už v Javě 8.

Otázka zní: Jak rychle můžeme vy­tvo­řit in­stanci kaž­dého z nich?

Abych to zod­po­vě­děl, napsal jsem malý JMH ben­chmark, který tes­tuje výrobu Date ob­jektů přes GregorianCalendar nebo s pomocí SimpleDateFormat a výrobu ob­jektů LocalDate přes DateTimeFormatter nebo přímo sta­tic­kou me­to­dou 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á­sle­du­jí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ě­ko­lik věcí:

  1. Zno­vupo­u­žití ob­jektů GregorianCalendar, SimpleDateFormatDateTimeFormatter je rych­lejší než jejich opa­ko­vané vy­tvá­ření. Někdy o čtvr­tinu, jindy víc než dva­krát. (To není pře­kva­pivé, z opa­ko­va­ného po­u­žití be­ne­fi­tuje na­pří­klad i XML­Stre­a­mWri­ter nebo regex Matcher).
  2. Par­so­vání stringu zabere ~300 ns času, víc než vy­tvo­ření in­stance Date nebo LocalDate
  3. LocalDate je 30x rych­lejší než staré API. Za to může fakt, že LocalDate je tro­jice čísel den, měsíc, rok, kdežto Date re­pre­zen­tuje čas jako kon­krétní GMT oka­mžik ve formě počtu mi­li­sekund. Proto musí při vy­tvá­ření brát v potaz časové zóny a musí projít lo­gi­kou gre­go­ri­án­ského ka­len­dáře, která není úplně tri­vi­ální. Ob­jektu LocalDate při vy­tvo­ření předám tři čísla, on je na­staví do vnitř­ních pro­měn­ných a má hotovo. Neví nic o ča­so­vých zónách a nemusí po­čí­tat vzdá­le­nost od ur­či­tého re­fe­renč­ního mo­mentu.

Navíc je nové java.time API im­mu­table a bez­pečné při po­u­žití ve více vlák­nech. Není tedy nutné se roz­ho­do­vat mezi rych­lostí a ma­chi­na­cemi s ThreadLocal pro­měn­nými.

Do­datky:

  1. Pokud po­tře­bu­jete pra­co­vat s daty jako ti­mestampy, pro­tože na­pří­klad musíte po­čí­tat kolik uběhlo mezi dvěma oka­mžiky vteřin/minut/hodin/dnů, nové API na to má třídu java.time.Instant. LocalDateTime musí ti­mestamp vždy spo­čí­tat, což není úplně tri­vi­ální.
  2. Na druhou stranu, po­rov­nání dvou LocalDateTime ob­jektů je stále velice rychlé.
píše k47 (@kaja47, k47)