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

Performance fixes for v2.0 and fix regression with out of order columns #691

Merged
merged 6 commits into from
Aug 5, 2023

Conversation

pradeepnschrodinger
Copy link
Collaborator

@pradeepnschrodinger pradeepnschrodinger commented Jul 13, 2023

Description

This refactors some of the code in our redux logic to improve performance for v2.
The main changes are:

  1. state.columnProps and state.columnGroupProps has been renamed to columnElements and columnGroupElements respectively.
  2. Some usages of _.pick, _.forEach, _.map, _.reduce, etc have been hand picked and replaced with faster calls (mostly native) for performance.
  3. columnWidths selector and convertColumnsToElements helper have been rewritten (keeping their structure same) to make them more efficient.
  4. Immer (used by redux toolkit) deeply freezes our redux store state. This is pretty inefficient for large data structures (eg: elementTemplates). Luckily, we can workaround this by manually pre-freezing them shallowly.

The refactor This also fixes a regression where FDT renders the wrong renderers (cell templates) when columns are specified in a different order than what gets rendered.
eg:

<Column
  columnKey="A"
  fixed={true}
/>
<Column
  columnKey="B"
  fixedRight={true}
/>
<Column
  columnKey="C"
/>

causes FDT to mistakenly render column C using the renderer from column B (and vice-versa).

How Has This Been Tested?

Tested on local examples.
Particularly the "Fixed Right" example which is rendered by passing columns out of order.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.

@pradeepnschrodinger pradeepnschrodinger changed the title Allow columns from different cell groups to be specified out of order Performance fixes for v2.0 and fix regression with out of order columns Jul 16, 2023
@pradeepnschrodinger pradeepnschrodinger added performance v2.0-beta Tickets targeting the v2.0-beta branch. labels Jul 16, 2023
Comment on lines +153 to +154
'columnGroupElements',
'columnElements',
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

state.columnProps has been replaced with state.columnElements.

One reason being that there's a modified version of columnProps returned by selectors, leading to confusion between state.columnProps and someSelector().columnProps.

Comment on lines +18 to +19
// NOTE (pradeep): This can be simplified via _.pick()
// However, _.pick is much slower than the hand written version here.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

for (const columnGroupReactElement of children) {
const cellGroupType = getCellGroupType(columnGroupReactElement);
const columnGroupProps = _extractProps(columnGroupReactElement);
columnGroupProps.index = columnGroupIndex;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also calculate and store the index while extracting the column/column-group.

Comment on lines +146 to +147
columnGroupElements,
columnElements,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

columnGroupElements groups column props by cellGroupType, where as columnProps is a flat ordered list of column prop objects.

@@ -12,16 +12,55 @@

'use strict';

import reduce from 'lodash/reduce';
import { createSelector } from 'reselect';
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

createSelector is used here to memoize the selectors.

}
const cacheOptions = {
memoizeOptions: {
maxSize: 12, // 3 cell group types * 4 template types
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This ensures we can call the selectors for each cell group type for each template type without having to recompute the widths.

@@ -164,55 +175,18 @@ function flexWidths(columnGroupProps, columnProps, viewportWidth) {
* scrollableColumns: !Array.<columnDefinition>
* }}
*/
function groupElements(elements) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Besides grouping, groupElements also assigned index and offset.
The extra work is no longer needed.

// if no flexGrow is specified, column defaults to original width
if (!flexGrow) {
return column;
function flexWidths(columnGroupElements, columnElements, viewportWidth) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The main change here is that the flexWidths also calculates the offsets of the column based on the new flex width.

This was previously done by groupElements() below.

@@ -6,6 +6,26 @@ import forEach from 'lodash/forEach';

import convertColumnElementsToData from '../../src/helper/convertColumnElementsToData';

function column(overrides) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

helper to create a column element

@@ -7818,7 +7818,12 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=

reselect@^4.0.0, reselect@^4.1.5:
reselect@^4.0.0:
version "4.1.8"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Bumped to latest version which supports cache customization for the selectors.

@@ -152,7 +152,7 @@ const slice = createSlice({
},
propChange(state, action) {
const { newProps, oldProps } = action.payload;
const oldState = clone(state);
const oldState = clone(original(state));
Copy link
Collaborator

Choose a reason for hiding this comment

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

what does original do

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

state is proxy object from immer that points to the actual redux state.
Iterating over this proxy object is slightly slower than iterating over the original state.

original(proxyObject) returns back the original object pointed to by the proxy.
(See original docs)

Object.freeze(state.columnElements);
Object.freeze(state.columnGroupElements);
Object.freeze(state.elementTemplates);

Copy link
Collaborator

Choose a reason for hiding this comment

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

nice

};
const columnElements = getElementsContainer();
const columnGroupElements = getElementsContainer();
const elementTemplates = getElementTemplates();
Copy link
Collaborator

Choose a reason for hiding this comment

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

just a thought shouldnt the function name be getEmptyElementContainer/ getDefaultElementContainer

or what you have done is fine?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

hmm both seem fine to me, but I'll rename it to getEmptyElementsContainer to be explicit.

@karry08
Copy link
Collaborator

karry08 commented Jul 31, 2023

LGTM

const columnGroupElements = getElementsContainer();
const elementTemplates = getElementTemplates();
const useGroupHeader = !!(
children.length && children[0].type.__TableColumnGroup__
Copy link
Collaborator

Choose a reason for hiding this comment

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

This can also be simplified by optional chaining.
const useGroupHeader = children[0]?.type.__TableColumnGroup__ ?? false

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

// pre-freeze data using the freeze utility.
Object.freeze(state.columnElements);
Object.freeze(state.columnGroupElements);
Object.freeze(state.elementTemplates);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can convertColumnElementsToData itself return these objects as frozen???

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We could, but I like to keep convertColumnElementsToData as a pure helper method that shouldn't know about immer/parent reducer details.

@AmanGupta2708
Copy link
Collaborator

will it merge to column-virtualization as well ?

@pradeepnschrodinger
Copy link
Collaborator Author

will it merge to column-virtualization as well ?

@AmanGupta2708 , yep this should be upmerged manually.
I don't think it'll be an easy merge though.
There are also lots of other commits in v2 that needs upmerging.
It makes sense to do them all, and perform another round of performance benchmarks.

@pradeepnschrodinger pradeepnschrodinger merged commit 8229e50 into v2.0-beta Aug 5, 2023
6 checks passed
@pradeepnschrodinger pradeepnschrodinger deleted the v20-beta-fix-outoforder-columns branch August 5, 2023 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug performance regression issue that was "fixed" but got reopened later on v2.0-beta Tickets targeting the v2.0-beta branch.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants