From d1a0dbc3d1a9a6cdedaeef114ff92046630e04be Mon Sep 17 00:00:00 2001 From: Kevin Schweikert Date: Thu, 12 Sep 2024 17:03:46 +0200 Subject: [PATCH 1/3] refactor to benchee --- .formatter.exs | 2 +- benchmark.exs | 62 ++++++++++++++++++++++++ java/GenerateTzData.java | 6 ++- lib/{mix/tasks/run.ex => benchmark.ex} | 66 ++++++++------------------ mix.exs | 9 ++-- mix.lock | 11 +++-- test/test_helper.exs | 1 - test/tzdb_test.exs | 4 -- 8 files changed, 99 insertions(+), 62 deletions(-) create mode 100644 benchmark.exs rename lib/{mix/tasks/run.ex => benchmark.ex} (71%) delete mode 100644 test/test_helper.exs delete mode 100644 test/tzdb_test.exs diff --git a/.formatter.exs b/.formatter.exs index d2cda26..dd2c5e5 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,4 +1,4 @@ # Used by "mix format" [ - inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] + inputs: ["{mix,.formatter,benchmark}.exs", "{config,lib,test}/**/*.{ex,exs}"] ] diff --git a/benchmark.exs b/benchmark.exs new file mode 100644 index 0000000..0575ea1 --- /dev/null +++ b/benchmark.exs @@ -0,0 +1,62 @@ +benchmarks = %{ + "time_zone_info" => { + fn input -> + Benchmark.run(:time_zone_info, TimeZoneInfo.iana_version(), input) + end, + before_scenario: fn input -> + Benchmark.start!(:time_zone_info) + input + end, + after_scenario: fn _ -> + Benchmark.stop!(:time_zone_info) + end + }, + "tz" => { + fn input -> Benchmark.run(:tz, Tz.iana_version(), input) end, + before_scenario: fn input -> + Benchmark.start!(:tz) + input + end, + after_scenario: fn _ -> + Benchmark.stop!(:tz) + end + }, + "zoneinfo" => { + fn input -> Benchmark.run(:tz, Benchmark.zoneifo_version(), input) end, + before_scenario: fn input -> + Benchmark.start!(:zoneinfo) + input + end, + after_scenario: fn _ -> + Benchmark.stop!(:zoneinfo) + end + }, + "tzdata" => { + fn input -> Benchmark.run(:tzdata, Tzdata.tzdata_version(), input) end, + before_scenario: fn input -> + Benchmark.start!(:tzdata) + input + end, + after_scenario: fn _ -> + Benchmark.stop!(:tzdata) + end + } +} + +benchmarks = + if System.find_executable("java") do + Map.merge(benchmarks, %{ + "java" => fn input -> + System.cmd("java", ["java/GenerateTzData.java", input]) + end + }) + else + benchmarks + end + +Benchee.run(benchmarks, + inputs: %{ + "standard" => "files/input", + "far future" => "files/input_far_future" + } +) diff --git a/java/GenerateTzData.java b/java/GenerateTzData.java index c2d36ef..4611f2a 100644 --- a/java/GenerateTzData.java +++ b/java/GenerateTzData.java @@ -67,6 +67,9 @@ private static String buildEntry(String timezone, LocalDateTime localDateTime) { } public static void main(String[] args) throws IOException { + if (args.length < 1) { + throw new IllegalArgumentException("Missing command line argument: input directory path."); + } String ianaTzVersion = java.time.zone.ZoneRulesProvider .getVersions("UTC") .lastEntry() @@ -75,7 +78,8 @@ public static void main(String[] args) throws IOException { System.out.println("IANA tz version: " + ianaTzVersion); String basePath = new File("").getAbsolutePath(); - String inputDir = Paths.get(basePath, "files/input/").toString(); + String input = args[0]; + String inputDir = Paths.get(basePath, input).toString(); String outputDir = Files.createDirectories(Paths.get(basePath, "files/output/", ianaTzVersion, "java")).toString(); diff --git a/lib/mix/tasks/run.ex b/lib/benchmark.ex similarity index 71% rename from lib/mix/tasks/run.ex rename to lib/benchmark.ex index f319be9..278154d 100644 --- a/lib/mix/tasks/run.ex +++ b/lib/benchmark.ex @@ -1,59 +1,35 @@ -defmodule Mix.Tasks.Tzdb.Run do - use Mix.Task - - @input "files/input" +defmodule Benchmark do + # @input "files/input" # @input "files/input_far_future" + @with_zone_abbr true - def run(command_line_args) do - {lib, module, version} = parse_args(command_line_args) - + def run(lib, version, input) do + module = lib |> Atom.to_string() |> Macro.camelize() time_zone_db = Module.concat(module, TimeZoneDatabase) - time = - measure(fn -> - generate_files( - time_zone_db, - version, - Path.join([File.cwd!(), "files/output", version, to_string(lib)]) - ) - end) - - Mix.shell().info("Time: #{round(time)} seconds") - end - - defp parse_args(["tz"]), do: {:tz, Tz, start!(:tz) && Tz.iana_version()} - - defp parse_args(["time_zone_info"]), - do: {:time_zone_info, TimeZoneInfo, start!(:time_zone_info) && TimeZoneInfo.iana_version()} - - defp parse_args(["zoneinfo"]), do: {:zoneinfo, Zoneinfo, start!(:zoneinfo) && zoneifo_version()} - defp parse_args(["tzdata"]), do: {:tzdata, Tzdata, start!(:tzdata) && Tzdata.tzdata_version()} - - defp parse_args(_) do - Mix.raise( - "command requires one argument: the name of the library to generate data for (tz, time_zone_info, zoneinfo, tzdata)" + generate_files( + time_zone_db, + version, + input, + Path.join([File.cwd!(), "files/output", version, to_string(lib)]) ) end - defp start!(lib) do + def start!(lib) do {:ok, _} = Application.ensure_all_started(lib) :ok end - defp zoneifo_version() do - {:ok, version} = File.read("/usr/share/zoneinfo/+VERSION") - String.trim(version) + def stop!(lib) do + :ok = Application.stop(lib) + :ok end - defp measure(function) do - function - |> :timer.tc() - |> elem(0) - |> Kernel./(1_000_000) + def zoneifo_version() do + {:ok, version} = File.read("/usr/share/zoneinfo/+VERSION") + String.trim(version) end - @with_zone_abbr true - defp date_time_to_string(dt) do dt_string = Calendar.strftime(dt, "%c") dt_string <> offset_to_string(dt.utc_offset + dt.std_offset) @@ -75,16 +51,14 @@ defmodule Mix.Tasks.Tzdb.Run do defp do_offset_to_string({h, m, 0}), do: :io_lib.format("~2..0B:~2..0B", [h, m]) defp do_offset_to_string({h, m, s}), do: :io_lib.format("~2..0B:~2..0B:~2..0B", [h, m, s]) - defp generate_files(time_zone_database, version, outputDir) do - inputDir = Path.join([File.cwd!(), @input]) + defp generate_files(time_zone_database, version, input, outputDir) do + inputDir = Path.join([File.cwd!(), input]) File.rm_rf!(outputDir) File.mkdir_p!(outputDir) File.ls!(inputDir) |> Enum.each(fn filename -> - Mix.shell().info("Generating data for file '#{filename}'...") - File.write!(Path.join(outputDir, filename), version <> "\n") File.stream!(Path.join(inputDir, filename)) @@ -101,8 +75,6 @@ defmodule Mix.Tasks.Tzdb.Run do end) |> Stream.into(File.stream!(Path.join(outputDir, filename), [:append])) |> Stream.run() - - Mix.shell().info("Done") end) end diff --git a/mix.exs b/mix.exs index ec35969..c0dd8c9 100644 --- a/mix.exs +++ b/mix.exs @@ -31,11 +31,12 @@ defmodule Tzdb.MixProject do defp deps do [ - {:tz, "~> 0.26.5"}, - {:time_zone_info, "~> 0.7.3"}, - {:tzdata, "~> 1.1.1"}, + {:tz, "~> 0.28.1"}, + {:time_zone_info, "~> 0.7.5"}, + {:tzdata, "~> 1.1.2"}, {:zoneinfo, "~> 0.1.8"}, - {:ex_doc, "~> 0.34", only: :dev} + {:ex_doc, "~> 0.34", only: :dev}, + {:benchee, "~> 1.0", only: :dev} ] end end diff --git a/mix.lock b/mix.lock index ba05f69..8989d05 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,7 @@ %{ + "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, @@ -8,13 +10,14 @@ "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "time_zone_info": {:hex, :time_zone_info, "0.7.3", "ab0ca1c249aef55794b3a71a9ba7184121f99436410f35a2525f67035c86c899", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7a549c7db4c7c1bd0fcac7dc8417c0b52a619950b0f495f4fb7c69a5302d1368"}, - "tz": {:hex, :tz, "0.26.6", "4d46178dd5bc4d2c1e78c9affcc3fd46764e29cd2a148c06666edb83cb18629f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "9ca97ea48b412f2404740867f6c321ee8ce112602035bb79b0b90c9c03174652"}, - "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, + "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, + "time_zone_info": {:hex, :time_zone_info, "0.7.5", "8ebfc6d0cc6a797a945344aa4cd81efe33bca15e6fb0f6178bc9011c988dbf2c", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "484a75bbb30ae4f34c59f8c35ac8e1832ce3862efc0944afdff0195e9a0e4028"}, + "tz": {:hex, :tz, "0.28.1", "717f5ffddfd1e475e2a233e221dc0b4b76c35c4b3650b060c8e3ba29dd6632e9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "bfdca1aa1902643c6c43b77c1fb0cb3d744fd2f09a8a98405468afdee0848c8a"}, + "tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "zoneinfo": {:hex, :zoneinfo, "0.1.8", "add41f44e6f19d4d16d9eebe3261d6307c58bdbe7cc61ffb7b18cad346b0467f", [:mix], [], "hexpm", "3999755971bbf85f0c8c75a724be560199bb63406660585849f0eb680e2333f7"}, } diff --git a/test/test_helper.exs b/test/test_helper.exs deleted file mode 100644 index 869559e..0000000 --- a/test/test_helper.exs +++ /dev/null @@ -1 +0,0 @@ -ExUnit.start() diff --git a/test/tzdb_test.exs b/test/tzdb_test.exs deleted file mode 100644 index 720111c..0000000 --- a/test/tzdb_test.exs +++ /dev/null @@ -1,4 +0,0 @@ -defmodule TzdbTest do - use ExUnit.Case - -end From eee08fa2c5350fdf4934ad9020468737e85f60b4 Mon Sep 17 00:00:00 2001 From: Kevin Schweikert Date: Thu, 12 Sep 2024 17:23:16 +0200 Subject: [PATCH 2/3] bring back mix task to generate output --- config/config.exs | 2 ++ lib/mix/tasks/run.ex | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 config/config.exs create mode 100644 lib/mix/tasks/run.ex diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..7d23bcf --- /dev/null +++ b/config/config.exs @@ -0,0 +1,2 @@ +import Config +config :tzdata, :autoupdate, :disabled diff --git a/lib/mix/tasks/run.ex b/lib/mix/tasks/run.ex new file mode 100644 index 0000000..865de30 --- /dev/null +++ b/lib/mix/tasks/run.ex @@ -0,0 +1,24 @@ +defmodule Mix.Tasks.Tzdb.Run do + use Mix.Task + + def run([lib, input]) do + run_benchmark(lib, input) + end + + def run(_) do + Mix.raise( + "command requires two argument: the name of the library to generate data for (tz, time_zone_info, zoneinfo, tzdata) and an input directory" + ) + end + + defp run_benchmark("tz", input), do: Benchmark.run(:tz, Tz.iana_version(), input) + + defp run_benchmark("time_zone_info", input), + do: Benchmark.run(:time_zone_info, TimeZoneInfo.iana_version(), input) + + defp run_benchmark("zoneinfo", input), + do: Benchmark.run(:zoneinfo, Benchmark.zoneifo_version(), input) + + defp run_benchmark("tzdata", input), + do: Benchmark.run(:tzdata, Tzdata.tzdata_version(), input) +end From c75f4733f95da53491c6f86d21503eeb8d0885f0 Mon Sep 17 00:00:00 2001 From: Kevin Schweikert Date: Thu, 12 Sep 2024 17:23:27 +0200 Subject: [PATCH 3/3] add benchmark results to README --- README.md | 91 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 0033630..38d0667 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,19 @@ This repository allows to perform time zone operations on a huge set of predefin ## Generate the Java result set -Generate the result for Java by executing `GenerateTzData.java` in `/java`. +Generate the result for Java by executing `java java/GenerateTzData.java`. The result is written in `/files/output`. ## Generate the Elixir libraries result set -Execute the mix task to generate the result for the Elixir libraries: +Execute the mix task to generate the result for the Elixir libraries (you can change the second argument to "files/input_far_future" if you want to switch the dataset): ```bash -mix tzdb.run tz -mix tzdb.run time_zone_info -mix tzdb.run zoneinfo -mix tzdb.run tzdata +mix tzdb.run tz "files/input" +mix tzdb.run time_zone_info "files/input" +mix tzdb.run zoneinfo "files/input" +mix tzdb.run tzdata "files/input" ``` The result is written in `/files/output`. @@ -44,21 +44,70 @@ At the time of writing this, ### Performance -Time spent generating the result is logged in the console, giving some idea of the difference in terms of performance. - -The time taken for each library to generate the output on my system are: -* ~50 seconds for `tz` -* ~50 seconds for `time_zone_info` -* ~80 seconds for `zoneinfo` -* ~160 seconds for `tzdata` - -System used: -* Operating system: macOS -* CPU: Apple M2 -* Available cores: 8 -* Available memory: 16 GB -* Elixir version: 1.14.1 -* Erlang version: 25.1.2 +The results from `mix run benchmark.exs` (removed Logging output): + +``` +Generated tzdb_test app +Operating System: macOS +CPU Information: Apple M3 Pro +Number of Available Cores: 11 +Available memory: 36 GB +Elixir 1.17.2 +Erlang 27.0.1 +JIT enabled: true + +Benchmark suite executing with the following configuration: +warmup: 2 s +time: 5 s +memory time: 0 ns +reduction time: 0 ns +parallel: 1 +inputs: far future, standard +Estimated total run time: 1 min 10 s + +Benchmarking java with input far future ... +Benchmarking java with input standard ... +Benchmarking time_zone_info with input far future ... +Benchmarking time_zone_info with input standard ... +Benchmarking tz with input far future ... +Benchmarking tz with input standard ... +Benchmarking tzdata with input far future ... +Benchmarking tzdata with input standard ... +Benchmarking zoneinfo with input far future ... +Benchmarking zoneinfo with input standard ... +Calculating statistics... +Formatting results... + +##### With input far future ##### +Name ips average deviation median 99th % +java 0.42 2.40 s ±1.91% 2.39 s 2.45 s +time_zone_info 0.0695 14.39 s ±0.00% 14.39 s 14.39 s +tz 0.0259 38.61 s ±0.00% 38.61 s 38.61 s +zoneinfo 0.0252 39.71 s ±0.00% 39.71 s 39.71 s +tzdata 0.0220 45.41 s ±0.00% 45.41 s 45.41 s + +Comparison: +java 0.42 +time_zone_info 0.0695 - 6.00x slower +11.99 s +tz 0.0259 - 16.10x slower +36.21 s +zoneinfo 0.0252 - 16.56x slower +37.31 s +tzdata 0.0220 - 18.94x slower +43.02 s + +##### With input standard ##### +Name ips average deviation median 99th % +java 0.183 5.47 s ±0.00% 5.47 s 5.47 s +zoneinfo 0.0416 24.04 s ±0.00% 24.04 s 24.04 s +tz 0.0408 24.51 s ±0.00% 24.51 s 24.51 s +time_zone_info 0.0405 24.67 s ±0.00% 24.67 s 24.67 s +tzdata 0.00771 129.72 s ±0.00% 129.72 s 129.72 s + +Comparison: +java 0.183 +zoneinfo 0.0416 - 4.40x slower +18.57 s +tz 0.0408 - 4.48x slower +19.04 s +time_zone_info 0.0405 - 4.51x slower +19.20 s +tzdata 0.00771 - 23.72x slower +124.25 s +``` ## How does it work?