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

[PERF] evaluation: use custom data structures instead of Map and Set #3823

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 0 additions & 16 deletions src/helpers/misc.ts
Expand Up @@ -461,22 +461,6 @@ export function isConsecutive(iterable: Iterable<number>): boolean {
return true;
}

export class JetSet<T> extends Set<T> {
addMany(iterable: Iterable<T>): this {
for (const element of iterable) {
super.add(element);
}
return this;
}
deleteMany(iterable: Iterable<T>): boolean {
let wasDeleted = false;
for (const element of iterable) {
wasDeleted ||= super.delete(element);
}
return wasDeleted;
}
}

/**
* Creates a version of the function that's memoized on the value of its first
* argument, if any.
Expand Down
98 changes: 98 additions & 0 deletions src/plugins/ui_core_views/cell_evaluation/binary_grid.ts
@@ -0,0 +1,98 @@
import { CellPosition } from "../../../types";

type Bit = 0 | 1;

/**
* Implements a fixed-sized grid or 2D matrix of bits.
* based on https://github.com/zandaqo/structurae
*
* The grid is implemented as a 1D array of 32-bit integers, where each bit represents a cell in the grid.
* It follows row-major order, with each row stored consecutively in 32-bit blocks.
* Pads the number of columns to the next power of 2 to allow quick lookups with bitwise operations.
*
* Key terminology:
* - bucket: Index of an item in the Uint32Array, a 32-bit integer.
* - bitPosition: The position of a bit within the bucket 32-bit integer.
*/
export class BinaryGrid extends Uint32Array {
private columnOffset = 0;
cols = 0;
rows = 0;

/**
* Creates a binary grid of specified dimensions.
*/
static create(rows: number, columns: number): BinaryGrid {
const columnOffset = log2Ceil(columns);
const length = (rows << columnOffset) >> 5;
const grid = new this(length + 1);
grid.columnOffset = columnOffset;
grid.cols = columns;
grid.rows = rows;
return grid;
}

/**
* Returns the bit at given coordinates.
*/
getValue(position: CellPosition): Bit {
const [bucket, bitPosition] = this.getCoordinates(position);
return ((this[bucket] >> bitPosition) & 1) as Bit;
}

/**
* Sets the bit at given coordinates.
*/
setValue(position: CellPosition, value: Bit) {
const [bucket, bitPosition] = this.getCoordinates(position);
const currentValue = (this[bucket] >> bitPosition) & 1;
const hasBeenInserted = currentValue === 0 && value === 1;
this[bucket] = (this[bucket] & ~(1 << bitPosition)) | (value << bitPosition);
return hasBeenInserted;
// Let's breakdown of the above line:
// with an example with a 4-bit integer (instead of 32-bit).
//
// Let's say we want to set the bit at position 2 to 1 and the existing
// bit sequence this[bucket] is 1001. The final bit sequence should be 1101.
//
// First, we clear the bit at position 2 by AND-ing this[bucket] with a
// mask having all 1s except a 0 at the bit position (~ (1 << bitPosition)).
// 1 << bitPosition is 0100 (shifting 0001 to the left by 2)
// Inverting the bits with ~ gives the final mask ~(1 << bitPosition): 1011
//
// Then, we shift the value by the bit position (value << bitPosition: 0100)
// and OR the result with the previous step's result:
// (1001 & 1011) | 0100 = 1101
}

isEmpty() {
return !this.some((bucket) => bucket !== 0);
}

fillAllPositions() {
const thirtyTwoOnes = -1 >>> 0; // same as 2 ** 32 - 1, a 32-bit number with all bits set to 1
this.fill(thirtyTwoOnes);
}

clear() {
this.fill(0);
}

private getCoordinates(position: CellPosition): [bucket: number, position: number] {
const { row, col } = position;
const index = (row << this.columnOffset) + col;
const bucket = index >> 5;
return [bucket, index - (bucket << 5)];
}
}

function log2Ceil(value: number) {
// A faster version of Math.ceil(Math.log2(value)).
if (value === 0) {
return -Infinity;
} else if (value < 0) {
return NaN;
}
// --value handles the case where value is a power of 2
return 32 - Math.clz32(--value);
}
@@ -1,5 +1,5 @@
import { compileTokens } from "../../../formulas/compiler";
import { Token, isExportableToExcel } from "../../../formulas/index";
import { isExportableToExcel, Token } from "../../../formulas/index";
import { getItemId, positions, toXC } from "../../../helpers/index";
import { CellErrorType } from "../../../types/errors";
import {
Expand All @@ -13,11 +13,11 @@ import {
Format,
FormattedValue,
FormulaCell,
invalidateDependenciesCommands,
Matrix,
Range,
UID,
Zone,
invalidateDependenciesCommands,
} from "../../../types/index";
import { FormulaCellWithDependencies } from "../../core";
import { UIPlugin, UIPluginConfig } from "../../ui_plugin";
Expand Down Expand Up @@ -189,6 +189,10 @@ export class EvaluationPlugin extends UIPlugin {
this.evaluator.updateDependencies(cmd);
}
break;
case "DUPLICATE_SHEET":
case "CREATE_SHEET":
this.shouldRebuildDependenciesGraph = true;
break;
case "EVALUATE_CELLS":
this.evaluator.evaluateAllCells();
break;
Expand Down