As previously noted, the touch-layout preprocessing stage for touch keyboards takes... remarkably longer than seems necessary. This PR serves to address parts of that. The main three changes:
Make object-properties less expensive by having a common prototype, rather than artificially copying 'em over.
Only use delete when actually necessary.
Pre-constructing key-event objects per key adds up a bit; what if we deferred building them until needed?
Now... why those three? And what led me to decide on those three?
To stress-test things, let's examine one load of gff_amharic on an SM-T350 / Galaxy Tab A:
1.33 seconds to pre-process the layout - including both the sanitize and polyfill stages
1.33 seconds to construct the actual OSK's element hierarchy.
43 layers in total (form factor: tablet)
That is quite a lot of time to start up gff_amharic... and that's after a lot of recent optimization work! Can we push things further? The first question to ask in that regard: where is all that time being spent?
Over 700 ms spent in key-level polyfill() call-frames + over 240ms spent in key-level sanitize stack-frames = about 70.6% of the pre-processing time being spent in those stack-frames alone. Obviously, that's where to look for big wins.
As a runner-up point, note that constructBaseKeyEvent - our key-event preprocessing step - costs a total additional 136 ms.
Within sanitize:
Key observations:
A not-insignificant amount of time is spent just... facilitating iteration across the key property names.
Sadly, this doesn't seem possible to optimize out... at least not when using a for...of construction.
delete calls are somewhat expensive - and upon further investigation, this is when the fields are already undefined in a great many cases!
Keying an object with a variable also comes with significant expense.
Once again, a not-insignificant amount of time is spent just... facilitating iteration across the key property names.
Constantly checking for + redefining the key properties is expensive.
key.hasOwnProperty: 45.6ms
getOwnPropertyDescriptor: 54.1 ms
Object.defineProperty: 448.9 ms(!)
I've also noticed that unicodeIDToText can be somewhat expensive for keyboards that heavily leverage it. That said... it's nowhere even close to the impact of the properties.
So, how to optimize things? Back to those big three changes:
Make object-properties less expensive by having a common prototype, rather than artificially copying 'em over.
Only use delete when actually necessary.
Pre-constructing key-event objects per key adds up a bit; what if we deferred building them until needed?
For the first item there, the easiest way to handle this... is to use proper class-construction, rather than attempting to reuse the objects. Per MDN, setting the prototype afterward is expensive... and even then, it didn't appear to actually enable our desired properties on the key-objects anyway. By having them defined only once, on the common prototype... we can get a dramatic speedup.
The important observable stat: the pre-processing stage now only takes 523ms, rather than 1330ms. That's 2.54x as fast!
For comparison, the OSK element construction took 1.43 seconds - about the same as before, though a tad slower. (We didn't really make changes there, and this is within normal variation.)
Other notable details:
Note that sanitize now takes more total time than polyfill! We weren't able to make a big impact there; we'd probably need a more explicit, compiler-friendly implementation to get faster here.
The total time spent within the polyfill section preprocessing the keys and subkeys is now only 153.6ms in total.
It is far, far faster to construct new objects of a proper key class type and use Object.assign than it is to try copy-pasting property definitions over:
As this PR has follow-ups doing further optimization...
Addresses parts of #11166.
As previously noted, the touch-layout preprocessing stage for touch keyboards takes... remarkably longer than seems necessary. This PR serves to address parts of that. The main three changes:
delete
when actually necessary.Now... why those three? And what led me to decide on those three?
To stress-test things, let's examine one load of
gff_amharic
on an SM-T350 / Galaxy Tab A:Corresponding Chrome profile, exported as json
Basic observable stats:
sanitize
andpolyfill
stagesThat is quite a lot of time to start up
gff_amharic
... and that's after a lot of recent optimization work! Can we push things further? The first question to ask in that regard: where is all that time being spent?Over 700 ms spent in key-level
polyfill()
call-frames + over 240ms spent in key-levelsanitize
stack-frames = about 70.6% of the pre-processing time being spent in those stack-frames alone. Obviously, that's where to look for big wins.As a runner-up point, note that
constructBaseKeyEvent
- our key-event preprocessing step - costs a total additional 136 ms.Within
sanitize
:Key observations:
for...of
construction.delete
calls are somewhat expensive - and upon further investigation, this is when the fields are alreadyundefined
in a great many cases!Within
polyfill
:Key observations:
key.hasOwnProperty
: 45.6msgetOwnPropertyDescriptor
: 54.1 msObject.defineProperty
: 448.9 ms(!)unicodeIDToText
can be somewhat expensive for keyboards that heavily leverage it. That said... it's nowhere even close to the impact of the properties.So, how to optimize things? Back to those big three changes:
delete
when actually necessary.For the first item there, the easiest way to handle this... is to use proper class-construction, rather than attempting to reuse the objects. Per MDN, setting the prototype afterward is expensive... and even then, it didn't appear to actually enable our desired properties on the key-objects anyway. By having them defined only once, on the common prototype... we can get a dramatic speedup.
The important observable stat: the pre-processing stage now only takes 523ms, rather than 1330ms. That's 2.54x as fast!
Other notable details:
sanitize
now takes more total time thanpolyfill
! We weren't able to make a big impact there; we'd probably need a more explicit, compiler-friendly implementation to get faster here.polyfill
section preprocessing the keys and subkeys is now only 153.6ms in total.class
type and useObject.assign
than it is to try copy-pasting property definitions over:As this PR has follow-ups doing further optimization...
@keymanapp-test-bot skip