Skip to content

Commit

Permalink
fix: Improve grouping performance (#4495)
Browse files Browse the repository at this point in the history
* When grouping data that has large clusters of values, generating grouped columns becomes extremely slow. For instance: 50k rows, 3 grouped columns, each grouped column has an identical value (so each groups together 50k rows each) - takes ~30 seconds

* The performance bottleneck is `[...previous, row]` - it has to recreate the previous array over and over again, getting slower and slower each time. Using `.push` instead takes it from 30 seconds down to < 100ms to generate the groupings (overall table generation goes down to < 1 second).

* `push` is safe in this case because all of the data is constructed in the `groupBy` method so we are not mutating data from anywhere that would require generating new arrays using spread every iteration
  • Loading branch information
jpcamara committed Oct 31, 2022
1 parent 23718d4 commit 77eef43
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 4 deletions.
2 changes: 1 addition & 1 deletion packages/react-table/__tests__/makeTestData.ts
@@ -1,4 +1,4 @@
import faker from 'faker'
import faker from '@faker-js/faker'

export type Person = {
firstName: string
Expand Down
47 changes: 47 additions & 0 deletions packages/table-core/__tests__/getGroupedRowModel.test.ts
@@ -0,0 +1,47 @@
import { ColumnDef, getCoreRowModel } from '../src';
import { createColumnHelper } from '../src/columnHelper';
import { createTable } from '../src/core/table';
import { getGroupedRowModel } from '../src/utils/getGroupedRowModel';
import { makeData, Person } from './makeTestData';

type personKeys = keyof Person;
type PersonColumn = ColumnDef<Person, string | number | Person[] | undefined>;

function generateColumns(people: Person[]): PersonColumn[] {
const columnHelper = createColumnHelper<Person>();
const person = people[0];
return Object.keys(person).map((key) => {
const typedKey = key as personKeys;
return columnHelper.accessor(typedKey, { id: typedKey });
});
}

describe('#getGroupedRowModel', () => {
it('groups 50k rows and 3 grouped columns with clustered data in less than 2 seconds', () => {
const data = makeData(50000);
const columns = generateColumns(data);
const grouping = ['firstName', 'lastName', 'age'];
const start = new Date();

data.forEach((p) => p.firstName = 'Fixed')
data.forEach((p) => p.lastName = 'Name')
data.forEach((p) => p.age = 123)

const table = createTable<Person>({
onStateChange() {},
renderFallbackValue: '',
data,
state: { grouping },
columns,
getCoreRowModel: getCoreRowModel(),
getGroupedRowModel: getGroupedRowModel()
})
const groupedById = table.getGroupedRowModel().rowsById;
const end = new Date();

expect(groupedById['firstName:Fixed'].leafRows.length).toEqual(50000)
expect(groupedById['firstName:Fixed>lastName:Name'].leafRows.length).toEqual(50000)
expect(groupedById['firstName:Fixed>lastName:Name>age:123'].leafRows.length).toEqual(50000)
expect(end.valueOf() - start.valueOf()).toBeLessThan(2000);
});
})
4 changes: 2 additions & 2 deletions packages/table-core/__tests__/makeTestData.ts
@@ -1,4 +1,4 @@
import faker from 'faker'
import faker from '@faker-js/faker'

export type Person = {
firstName: string
Expand All @@ -11,7 +11,7 @@ export type Person = {
}

const range = (len: number) => {
const arr = []
const arr: number[] = [];
for (let i = 0; i < len; i++) {
arr.push(i)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/table-core/src/utils/getGroupedRowModel.ts
Expand Up @@ -176,7 +176,7 @@ function groupBy<TData extends RowData>(rows: Row<TData>[], columnId: string) {
if (!previous) {
map.set(resKey, [row])
} else {
map.set(resKey, [...previous, row])
previous.push(row)
}
return map
}, groupMap)
Expand Down

0 comments on commit 77eef43

Please sign in to comment.