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
ptomato
left a comment
There was a problem hiding this comment.
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.
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.
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.
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
ptomato
left a comment
There was a problem hiding this comment.
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.
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.
@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.
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
ptomato
left a comment
There was a problem hiding this comment.
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.
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.
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 ObjectAssigns 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.
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
toLocaleStringand other operations that depend on the polyfilledIntl.DateTimeFormat. The fix was to prevent unnecessary creation of built-inIntl.DateTimeFormatobjects, 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.