Skip to content
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

Editor: Cache block meta-sourcing for getBlock selector #12555

Closed
wants to merge 2 commits into from

Conversation

aduth
Copy link
Member

@aduth aduth commented Dec 3, 2018

This pull request seeks to optimize the implementation of getBlock, avoiding repeated iterations of a block's attributes to determine whether a meta-sourced attribute exists. Prior to these changes, every call to getBlock would iterate each of a block's attributes to determine whether it needed to provide a value from post meta. As there are very few blocks to which this behavior applies, it's largely wasted effort, particularly considering that, since the block type won't change, we especially need not consider block types after it can be determined for the first time that no meta-sourced attributes exist.

Implementation notes:

There are many different ways we could go about solving this general issue of attributes derivation for meta attributes. Previously I'd considered some larger refactorings of block attributes sourcing (#5077). This is something which isn't included here, and should still be considered separately (#4989). The changes here are a more direct remedy to the logic of the current implementation.

Even within such a direct solution, I'm open to suggestions for improvement. I'd considered a few options with code placement of the utilities, and optionally selectors within the blocks module to detect whether attributes matching certain predicates exist. It's worth noting that the foundational block has no consideration of meta sources; this is a concept introduced by the editor. Thus, the closest I had got to making this a block concern was by introducing a getAttributeNamesBySource selector, which was both needlessly specific and not as impactful (easily-cached) as what's considered here.

The advantage of using a WeakMap cache is that it doesn't require manual invalidation, and respects the assumed immutability of a block type once registered. The downside as implemented is that it's another layer of caching, and introduces a few new utility functions which don't fall neatly into existing patterns (hasMetaSourcedAttribute is not quite a selector, createWeakCache could stand alone as its own module).

Testing instructions:

There should be no impact on general use of the editor, namely those behaviors which touch getBlockAttributes (keypress in paragraph). Confirm also no regressions on the behavior of blocks whose attributes are sourced from meta.†

† No meta-sourced core blocks exist. You may consider this example Stars block, with revisions to bring it up to date (add source: 'meta' to block type definition)

@aduth aduth added the [Type] Performance Related to performance efforts label Dec 3, 2018
@aduth aduth requested review from mcsf and youknowriad December 3, 2018 17:13
*
* @return {boolean} Whether block type has an meta-sourced attribute.
*/
const hasMetaSourcedAttribute = createWeakCache( ( blockType ) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can‘t we just use memoize instead of rolling our own? We already use it elsewhere, e.g. the i18n package.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a small but important difference in how this works using WeakMap as the cache storage, where normal memoization would hold on to values in memory forever, this would allow garbage collection to occur.

The second inconvenience is a memory leak because the arrays ensure that references to each key and each value are maintained indefinitely. These references prevent the keys from being garbage collected, even if there are no other references to the object. This would also prevent the corresponding values from being garbage collected.

memoized = memoize( _.stubTrue );
weakCached = createWeakCache( _.stubTrue );

var obj = { a: 1 };
weakCached( obj );
// [ { a: 1 } => true ]
memoized( obj );
// [ { a: 1 } => true ]

obj = { b: 2 };

weakCached( obj );
// [ { b: 2 } => true ]
memoized( obj );
// [ { a: 1 } => true, { b: 2 } => true ]
//   ┃
//   ┗━ This will be stuck in memory forever, despite there being no other
//      reference to the object.

For blocks, we'd really only achieve some significant benefit if we expect blocks to be overwritten at any point. Practically speaking this probably wouldn't occur, so we could potentially be fine with a standard memoization, albeit with a very fragile guarantee against memory leaks.

rememo (createSelector) has some internal tricks to achieve a similar effect, so there might be some options there, but it'd be quite indirect.

Copy link
Member Author

@aduth aduth Dec 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lodash's documentation includes a note about swapping its underlying cache implementation, though as described it appears to apply globally. Each memoized function has its own cache, so I'd wonder if it might be possible to assign to it directly.

https://lodash.com/docs/4.17.11#memoize

Maybe possible?

const hasMetaSourcedAttribute = memoize( ( blockType ) => { /* ... */ } );
hasMetaSourcedAttribute.cache = new WeakMap;

Looking at the implementation, I think this would work.

@youknowriad youknowriad added this to the 4.9 milestone Dec 21, 2018
@aduth aduth mentioned this pull request Jan 30, 2019
5 tasks
@gziolo
Copy link
Member

gziolo commented Feb 6, 2019

@aduth and @youknowriad - is it still something we want to land? We keep moving it between milestones so I'm wondering what is the reason.

@aduth
Copy link
Member Author

aduth commented Feb 6, 2019

I'm wondering what is the reason

Because nobody gives it a review 😄

I think it can be an impactful change for performance, but it feels a little wrong to be introducing some unmanaged side-effect state.

There was some recent related conversation with a reference to the work here at #13088 (comment) . What's described there feels more comfortable, but is also more difficult to implement. The work here was meant more as a band-aid fix. It could prove valuable as a temporary measure if #13088 is meant to be pushed through in a rush, but otherwise I think in our current position we're not as stressed for this to be a solution when there are other, more-thought-out options available.

To that end, I might close it at least until we decide what we want to do with it.

@aduth aduth closed this Feb 6, 2019
@aduth aduth deleted the update/get-block-cache-meta-sourced branch February 6, 2019 21:24
@gziolo
Copy link
Member

gziolo commented Feb 7, 2019

I'm wondering what is the reason

Because nobody gives it a review 😄

A pretty prosaic one :) These changes look complex so maybe that's why. In general, performance optimizations need a lot of time to be properly reviewed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Performance Related to performance efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants