-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Speed up toLocaleString
~2.5x by reducing creation of Intl.DateTimeFormat
instances
#12
Speed up toLocaleString
~2.5x by reducing creation of Intl.DateTimeFormat
instances
#12
Conversation
Intl.DateTimeFormat
instancestoLocaleString
~2.5x by reducing creation of Intl.DateTimeFormat
instances
481fffc
to
bfac998
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this looks good!
lib/intl.mjs
Outdated
if (!(this instanceof DateTimeFormat)) return new DateTimeFormat(locale, options); | ||
const original = new IntlDateTimeFormat(locale, options); | ||
const ro = original.resolvedOptions(); | ||
options = typeof options === 'undefined' ? {} : ObjectAssign({}, options); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
options would no longer be undefined
here unless the caller explicitly passed undefined
, I think? Maybe we should have options = undefined
as the function default argument. It doesn't really matter much though. I believe this is correct as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I made exactly this change in my latest checkin. I also realized that shallow-cloning of options
was not enough, because if options
property values or locale
were not primitives, then they could also be mutated by the caller before lazy-init happens. Take a look at my last checkin and let me know if the hacky JSON-based deep clone is sufficient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also added tests to verify that mutating constructor inputs after the constructor returns won't break anything.
Still not sure that using a JSON round-trip is the right way to deep-clone these inputs.
0092f38
to
b268b7a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly to you, I am feeling a bit iffy about the deep-cloning, but maybe it can be avoided altogether?
lib/intl.mjs
Outdated
return val; | ||
} | ||
|
||
function deepClone(val) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to avoid the deep cloning altogether, and store ro
instead of options
for lazily creating new instances? (and use ro.locale
as the locale?)
I ask because either deep- or shallow-cloning the object is going to be very tricky to do while matching the observable behaviour in ToDateTimeOptions. But it seems like no matter whether we create a DateTimeFormat with the originally-given or the resolved options/locale, it should have the same result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ryzokuken @sffc - @ptomato's idea above sounds like a good one. Will there be any difference in behavior between i1
and i2
below?
i1 = new DateTimeFormat(locale, options);
ro = i1.resolvedOptions();
i2 = new DateTimeFormat(ro.locale, ro);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: using resolvedOptions()
output to replace options
didn't work, because the polyfill adds additional options if the user's options don't contain unit options like year
or hour
.
What does seem to work is a small variation of this approach: clone resolvedOptions()
and delete all properties that weren't present in the original options object.
@ptomato - take a look at my latest commit and LMK if you think this is a promising approach.
b268b7a
to
8816dbf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this solution is good, though maybe one of the Intl folks could chip in?
lib/intl.mjs
Outdated
if (typeof options !== 'undefined') { | ||
const clonedOptions = ObjectAssign({}, ro); | ||
for (const prop in clonedOptions) { | ||
if (typeof options[prop] === 'undefined') delete clonedOptions[prop]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use ES.HasOwnProperty()
here, to avoid calling getters a second time. That will still not quite be spec-compliant, since Proxy objects would be able to observe the HasProperty operation, but it's probably fine. Although that might mean we shouldn't backport this to proposal-temporal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use
ES.HasOwnProperty()
here, to avoid calling getters a second time. That will still not quite be spec-compliant, since Proxy objects would be able to observe the HasProperty operation, but it's probably fine. Although that might mean we shouldn't backport this to proposal-temporal.
@ptomato I looked at the current polyfill code and I think that it has the multi-getter-call problem too. Look at the amend
function. I don't think the current polyfill has the Proxy problem because it ObjectAssign
s the options object before doing anything with it.
Anyway, I just pushed a new commit that does the options cloning (like the old polyfill) before reading, and also uses ES.HasOwnProperty()
to avoid making the proxy multi-getter problem any worse than it already is. I'll leave it for someone else to fix amend
later. Is this OK?
Also, I think this should be safe for back-porting because it doesn't make the current polyfill worse. What do you think?
LMK if you (or @ryzokuken @sffc) have any ideas how to improve this further.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was planning to take a look at the handling in the spec polyfill at some point. I guess we can accept this as "don't pass silly arguments" here, though.
f065e51
to
e646297
Compare
I think this PR is ready to merge, but I'm going to hold off on merging it for a day or so so that we can port and merge the now=>Now rename (see tc39/proposal-temporal#1645) first. As soon as that's merged in the old repo, I'll prepare a port of it for this repo. |
Speed up toLocaleString ~2.5x by optimizing creation of Intl.DateTimeFormat instances. Port of js-temporal/temporal-polyfill#12
Speed up toLocaleString ~2.5x by optimizing creation of Intl.DateTimeFormat instances. Port of js-temporal/temporal-polyfill#12
This commit speeds up toLocaleString and other operations that depend on the polyfilled Intl.DateTimeFormat. The fix was to prevent unnecessary creation of built-in Intl.DateTimeFormat objects, because the constructor of that built-in class is slooooooow. For more details about the underlying issue see: https://bugs.chromium.org/p/v8/issues/detail?id=6528 In local testing, speedup is about 2.5x for ZDT toLocaleString calls.
e646297
to
6680df7
Compare
Speed up toLocaleString ~2.5x by optimizing creation of Intl.DateTimeFormat instances. Port of js-temporal/temporal-polyfill#12
This PR speeds up
toLocaleString
and other operations that depend on the polyfilledIntl.DateTimeFormat
. The fix was to prevent unnecessary creation of built-inIntl.DateTimeFormat
objects, because the constructor of that built-in class is slooooooow. For more details about the underlying issue see: https://bugs.chromium.org/p/v8/issues/detail?id=6528In local testing, speedup is about 2.5x for ZDT toLocaleString calls. The sample code below runs in about 1800ms in the playground of the current main branch, and about 700ms in the playground of the PR branch.