moment / luxon

⏱ A library for working with dates and times in JS
https://moment.github.io/luxon
MIT License
15.26k stars 731 forks source link

`toRelative` is really slow #959

Open andreialecu opened 3 years ago

andreialecu commented 3 years ago

Describe the bug

Not necessarily a bug, but toRelative() is really slow and the reason isn't the Intl api.

I'm using luxon in a react-native app, and when formatting dates on a mid-level Android phone, a single call to .toRelative() takes around 15 milliseconds to return.

When you have a bigger list, this absolutely kills performance.

I tracked it down to this function: https://github.com/moment/luxon/blob/9a7e46b6e635db89c1c94e18a8c53c28341b7c5e/src/datetime.js#L1782-L1802

You can also verify this by running the built in benchmark in luxon at: https://github.com/moment/luxon/blob/e0c8f874304cd4ecc3944bdcff3d8f8c27102a18/benchmarks/datetime.js#L59-L61

Which can only do around 10000 operations per second on my M1 cpu in NodeJS. Others functions in that benchmark can do hundreds of thousands, so it's definitely a problem.

To Reproduce

Run the benchmark at https://github.com/moment/luxon/blob/e0c8f874304cd4ecc3944bdcff3d8f8c27102a18/benchmarks/datetime.js#L59-L61

Actual vs Expected behavior

Should not be so slow.

Desktop (please complete the following information):

Additional context

Note that using https://www.npmjs.com/package/javascript-time-ago is much much faster (less than 1 ms per format on the same Android device), so I'm sure some improvements are possible

andreialecu commented 3 years ago

Here's a quick benchmark:

const Benchmark = require("benchmark");
const { DateTime } = require("luxon");

const TimeAgo = require("javascript-time-ago");
const en = require("javascript-time-ago/locale/en");

const suite = new Benchmark.Suite();

const dt = DateTime.now().minus({ days: 42 });
const date = dt.toJSDate();

TimeAgo.addLocale(en)
const ta = new TimeAgo("en");

console.log(dt.toRelative());
console.log(ta.format(date));

suite
  .add("luxon", () => {
    dt.toRelative();
  })
  .add("javascript-time-ago", () => {
    ta.format(date);
  })
  .on("cycle", (event) => {
    console.log(String(event.target));
  })
  .on("complete", function () {
    console.log("Fastest is " + this.filter("fastest").map("name"));
  })
  .run();

Results:

1 month ago
1 month ago
luxon x 12,427 ops/sec ±1.25% (89 runs sampled)
javascript-time-ago x 249,242 ops/sec ±3.58% (93 runs sampled)
Fastest is javascript-time-ago
vladyslavNiemtsev commented 2 years ago

Hi @andreialecu ! Thanks for your investigation, it's helpful. But your Luxon version that you tried currently is too old.

Could you please try it again and show the results?

andreialecu commented 2 years ago
➜ node benchmark.js
luxon x 59,360 ops/sec ±0.69% (94 runs sampled)
javascript-time-ago x 366,422 ops/sec ±0.28% (100 runs sampled)
Fastest is javascript-time-ago

Still 6x slower than javascript-time-ago with luxon 2.3.0. Seems faster than before however.

vladyslavNiemtsev commented 2 years ago

Oh, thanks for this such a quick answer!

vladyslavNiemtsev commented 2 years ago

@andreialecu could you please tip me with following:

As I understand toRelative() function can't display the label like "just now" (as javascript-time-ago can) for rounded seconds, or for units array that we can pass as options into toRelative() like:

[ "years", "quarters", "months", "weeks", "days", "hours", "minutes"]

https://moment.github.io/luxon/api-docs/index.html#datetimetorelative

?

andreialecu commented 2 years ago

I ended up not using toRelative() because of the performance problems with it on React Native Android, but as far as I remember it cannot display just now, no.

vladyslavNiemtsev commented 2 years ago

Okay, thanks! @andreialecu

n0099 commented 2 weeks ago
cat <<'EOF' > package.json && yarn && yarn node benchmark.js # assume benchmark.js is https://github.com/moment/luxon/issues/959#issuecomment-864511181
{
  "dependencies": {
    "benchmark": "^2.1.4",
    "javascript-time-ago": "^2.5.10",
    "luxon": "^3.5.0"
  },
  "packageManager": "yarn@4.4.1"
}
EOF
1 month ago
1 month ago
luxon x 32,106 ops/sec ±13.77% (75 runs sampled)
javascript-time-ago x 196,248 ops/sec ±6.63% (76 runs sampled)
Fastest is javascript-time-ago

and its implement is untouched for years: https://github.com/moment/luxon/blame/3.5.0/src/datetime.js#L2202