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

[DataGrid] WASM-powered quick filter #9206

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/data/data-grid/filtering/QuickFilteringGridWasm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import { DataGrid, GridToolbar } from '@mui/x-data-grid';
import { useDemoData } from '@mui/x-data-grid-generator';

export default function QuickFilteringGridWasm() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 100_000,
});

return (
<Box sx={{ height: 500, width: 1 }}>
<DataGrid
{...data}
density="compact"
experimentalFeatures={{ wasmQuickFilter: true }}
disableColumnFilter
disableColumnSelector
disableDensitySelector
slots={{ toolbar: GridToolbar }}
slotProps={{
toolbar: {
showQuickFilter: true,
quickFilterProps: {
debounceMs: 50,
},
},
}}
/>
</Box>
);
}
33 changes: 33 additions & 0 deletions docs/data/data-grid/filtering/QuickFilteringGridWasm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import { DataGrid, GridToolbar } from '@mui/x-data-grid';
import { useDemoData } from '@mui/x-data-grid-generator';

export default function QuickFilteringGridWasm() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 100_000,
})

return (
<Box sx={{ height: 500, width: 1 }}>
<DataGrid
{...data}
density='compact'
experimentalFeatures={{ wasmQuickFilter: true }}
disableColumnFilter
disableColumnSelector
disableDensitySelector
slots={{ toolbar: GridToolbar }}
slotProps={{
toolbar: {
showQuickFilter: true,
quickFilterProps: {
debounceMs: 50,
},
},
}}
/>
</Box>
);
}
4 changes: 4 additions & 0 deletions docs/data/data-grid/filtering/quick-filter.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ In the following demo, the quick filter value `"Saint Martin, Saint Lucia"` will

{{"demo": "QuickFilteringCustomizedGrid.js", "bg": "inline", "defaultCodeOpen": false}}

## WASM demo

{{"demo": "QuickFilteringGridWasm.js", "bg": "inline", "defaultCodeOpen": false}}

## API

- [GridToolbarQuickFilter](/x/api/data-grid/grid-toolbar-quick-filter/)
Expand Down
4 changes: 4 additions & 0 deletions docs/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ module.exports = withDocsInfra({
replace: 'MTU5NjMxOTIwMDAwMA==', // 2020-08-02
},
},
{
test: /\.wasm$/,
type: 'asset/resource',
},
]),
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const getAggregationCellValue = ({

const values: any[] = [];
rowIds.forEach((rowId) => {
if (aggregationRowsScope === 'filtered' && filteredRowsLookup[rowId] === false) {
if (aggregationRowsScope === 'filtered' && !filteredRowsLookup[rowId]) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const gridExpandedSortedRowEntriesSelector = createSelector(
gridVisibleRowsLookupSelector,
gridSortedRowEntriesSelector,
(visibleRowsLookup, sortedRows) =>
sortedRows.filter((row) => visibleRowsLookup[row.id] !== false),
sortedRows.filter((row) => visibleRowsLookup[row.id] === true),
);

/**
Expand All @@ -83,7 +83,7 @@ export const gridFilteredSortedRowEntriesSelector = createSelector(
gridFilteredRowsLookupSelector,
gridSortedRowEntriesSelector,
(filteredRowsLookup, sortedRows) =>
sortedRows.filter((row) => filteredRowsLookup[row.id] !== false),
sortedRows.filter((row) => filteredRowsLookup[row.id] === true),
);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GridFilterItem, GridLogicOperator } from '../../../models/gridFilterItem';
import { GridFilterModel } from '../../../models/gridFilterModel';
import { GridRowId } from '../../../models/gridRows';
import { QuickFilterParams } from './quickFilter'

export type GridFilterItemResult = { [key: Required<GridFilterItem>['id']]: boolean };
export type GridQuickFilterValueResult = { [key: string]: boolean };
Expand Down Expand Up @@ -29,6 +30,10 @@ export interface GridFilterState {
* If a row is not registered in this lookup, it is supposed to have no descendant passing the filters.
*/
filteredDescendantCountLookup: Record<GridRowId, number>;
/**
* Quick filter buffers
*/
quickFilterBuffers: QuickFilterParams;
}

export interface GridFilterInitialState {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Makefile
#

WFLAGS := -Wl,--export-all -Wl,--no-entry -Wl,--allow-undefined
CFLAGS := --target=wasm32-unknown-unknown-wasm --optimize=3 -nostdlib $(WFLAGS)

all: build_simd

build_simd:
clang++ $(CFLAGS) module.cc --output module-simd128.wasm -msimd128 -DSIMD

build_nosimd:
clang++ $(CFLAGS) module.cc --output module-nosimd.wasm


# vim:ft=make
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// @ts-ignore
import moduleURL from './module-simd128.wasm'

export type QuickFilterParams = ReturnType<typeof createBuffers>;

const PAGE_SIZE = 64 * 1024;
const MAX_SEARCH_LENGTH = 256;

export enum Operator {
OR = 1,
AND = 2,
}

const decoder = new TextDecoder()
const encoder = new TextEncoder()

let resolve: any;
export const ready = new Promise((r) => {
resolve = r;
})

let instance: any

export async function setup() {
const result = await WebAssembly.instantiateStreaming(fetch(moduleURL), {
env: {
log_string: (pointer: number, length: number) => {
const memory = instance.memory as WebAssembly.Memory;
const buffer = new Uint8Array(memory.buffer, pointer, length);
const value = decoder.decode(buffer)
console.log(value)
},
log_number: (value: number) => {
console.log(value)
},
}
})
instance = result.instance.exports;
resolve()
}

if (typeof window !== 'undefined') {
setup()
}

function encodeString(value: string) {
const buffer = encoder.encode('\0'.repeat(4) + value)
const buffer32 = new Uint32Array(buffer.buffer, 0, 1)
buffer32[0] = buffer.length - 4;
return buffer
}

export function createBuffers(rows: string[][]) {
const buffers = [] as Uint8Array[];
let inputRealLength = 0

for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const buffer = encodeString(row.join(' ').toLowerCase())
buffers.push(buffer)
inputRealLength += buffer.length
}
const inputTotalLength = inputRealLength + (8 - inputRealLength % 8); // 8-bytes padded

const outputSlots = rows.length
const outputTotalLength = outputSlots * 4;
const searchLength = MAX_SEARCH_LENGTH;
const totalLength = inputTotalLength + outputTotalLength + searchLength;

const memory = instance.memory as WebAssembly.Memory;
const memoryLength = memory.buffer.byteLength;
if (memoryLength < totalLength) {
const missingByteLength = totalLength - memoryLength;
const missingPageLength = Math.ceil(missingByteLength / PAGE_SIZE)
memory.grow(missingPageLength);
}

console.log({
memory: memory.buffer.byteLength,
inputRealLength,
inputTotalLength,
outputSlots,
})
const inputBuffer = new Uint8Array(memory.buffer, 0, inputRealLength);
const outputBuffer = new Uint32Array(memory.buffer, inputTotalLength, outputSlots);
const searchBuffer = new Uint8Array(memory.buffer, inputTotalLength + outputTotalLength, searchLength);

let offset = 0;
for (let i = 0; i < buffers.length; i++) {
const buffer = buffers[i];
inputBuffer.set(buffer, offset)
offset += buffer.length;
}

return {
rows,
input: inputBuffer,
output: outputBuffer,
search: searchBuffer,
};
}

export function run(params: QuickFilterParams, search: string[], operator: Operator) {
let searchOffset = 0
search.forEach(term => {
const buffer = encodeString(term.toLowerCase());
params.search.set(buffer, searchOffset)
searchOffset += buffer.length;
})

const totalMatches = instance.run(
params.input.byteOffset,
params.input.length,
params.output.byteOffset,
params.output.length,
params.search.byteOffset,
search.length,
operator,
);

const indexes = params.output.slice(0, totalMatches)

return indexes
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include <stddef.h>
#include <stdint.h>
#include <limits.h>
#include <wasm_simd128.h>
#include "string_contains.cc"

extern "C" void log_string(const char *string, int string_length);
extern "C" void log_number(int value);
#define log(s) log_string(s, sizeof(s))

#define OPERATOR_OR 1
#define OPERATOR_AND 2


__attribute__((used)) extern "C" size_t run(
uint8_t *input, int input_length,
uint32_t *output, int output_length,
uint8_t *search, int search_length,
int op
) {
uint8_t *input_end = input + input_length;

uint32_t current_row_index = -1;
uint32_t row_text_length = -1;
uint8_t *row_text = NULL;

uint8_t *current = input;
uint32_t *current_output = output;

uint32_t total_matches = 0;

while (current < input_end) {
current_row_index += 1;
row_text_length = *((uint32_t*)current);
current += 4;
row_text = current;
current += row_text_length;

uint8_t *current_search = search;
for (int i = 0; i < search_length; i++) {
uint32_t search_term_length = *((uint32_t*)current_search);
current_search += 4;
uint8_t *search_term = current_search;
current_search += search_term_length;

bool did_match = string_contains(
(const char *)row_text, row_text_length,
(const char *)search_term, search_term_length
);

if (did_match && op == OPERATOR_OR) {
total_matches += 1;
*current_output++ = current_row_index;
break;
}
if (!did_match && op == OPERATOR_AND) {
goto row_loop;
}
}
if (op == OPERATOR_AND) {
total_matches += 1;
*current_output++ = current_row_index;
}
row_loop:;
}

return total_matches;
}

int main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#ifdef SIMD
#include "string_contains_simd128.cc"
#else
#include "string_contains_simple.cc"
#endif
Loading