Skip to content

Commit

Permalink
IMPROVE index string performance
Browse files Browse the repository at this point in the history
  • Loading branch information
pubkey committed Apr 7, 2023
1 parent 6c505f2 commit 8cb2872
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 42 deletions.
5 changes: 5 additions & 0 deletions orga/performance-trackings.md
Original file line number Diff line number Diff line change
Expand Up @@ -1354,3 +1354,8 @@ AFTER (fix getNumberIndexString()):
76.63579297065735
63.062547981739044
80.47493699193001

AFTER(fix iteration)
70.70821499824524
70.67861998081207
71.3054929971695
93 changes: 56 additions & 37 deletions src/custom-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
* For some RxStorage implementations,
* we need to use our custom crafted indexes
* so we can easily iterate over them. And sort plain arrays of document data.
*
* We really often have to craft an index string for a given document.
* Performance of everything in this file is very important
* which is why the code sometimes looks strange.
* Run performance tests before and after you touch anything here!
*/

import { getSchemaByObjectPath } from './rx-schema-helper';
Expand All @@ -18,25 +23,32 @@ import {
import { INDEX_MAX, INDEX_MIN } from './query-planner';


/**
* Prepare all relevant information
* outside of the returned function
* from getIndexableStringMonad()
* to save performance when the returned
* function is called many times.
*/
type IndexMetaField<RxDocType> = {
// getValue() function
v: ObjectPathMonadFunction<RxDocType>;
// type
t: | 0 // string
| 1 // boolean
| 2 // number
;
// maxLength
ml: number;
// parsed lengths (only on number fields)
pl: ParsedLengths | undefined;
};

export function getIndexMeta<RxDocType>(
schema: RxJsonSchema<RxDocumentData<RxDocType>>,
index: string[]
) {
/**
* Prepare all relevant information
* outside of the returned function
* to save performance when the returned
* function is called many times.
*/
const fieldNameProperties: {
fieldName: string;
schemaPart: JsonSchema;
/*
* Only in number fields.
*/
parsedLengths?: ParsedLengths;
getValueFn: ObjectPathMonadFunction<RxDocType>;
}[] = index.map(fieldName => {
): IndexMetaField<RxDocType>[] {
const fieldNameProperties: IndexMetaField<RxDocType>[] = index.map(fieldName => {
const schemaPart = getSchemaByObjectPath(
schema,
fieldName
Expand All @@ -52,13 +64,21 @@ export function getIndexMeta<RxDocType>(
);
}

return {
fieldName,
schemaPart,
parsedLengths,
hasComplexPath: fieldName.includes('.'),
getValueFn: objectPathMonad(fieldName)
let typeId: IndexMetaField<RxDocType>['t'] = 2;
if (type === 'string') {
typeId = 0;
}
if (type === 'boolean') {
typeId = 1;
}

const ret: IndexMetaField<RxDocType> = {
v: objectPathMonad(fieldName),
t: typeId,
ml: schemaPart.maxLength ? schemaPart.maxLength : 0,
pl: parsedLengths
};
return ret;
});
return fieldNameProperties;
}
Expand All @@ -80,30 +100,31 @@ export function getIndexableStringMonad<RxDocType>(
index: string[]
): (docData: RxDocumentData<RxDocType>) => string {
const fieldNameProperties = getIndexMeta(schema, index);
const fieldNamePropertiesAmount = fieldNameProperties.length;


/**
* @hotPath Performance of this function is very critical!
*/
const ret = function (docData: RxDocumentData<RxDocType>): string {
let str = '';
for (let i = 0; i < fieldNameProperties.length; ++i) {
for (let i = 0; i < fieldNamePropertiesAmount; ++i) {
const props = fieldNameProperties[i];
const schemaPart = props.schemaPart;
const type = schemaPart.type;
let fieldValue = props.getValueFn(docData);
if (type === 'string') {
const typeId = props.t;
let fieldValue = props.v(docData);
if (typeId === 0) {
// is string
if (!fieldValue) {
fieldValue = '';
}
str += fieldValue.padEnd(schemaPart.maxLength as number, ' ');
} else if (type === 'boolean') {
str += fieldValue.padEnd(props.ml, ' ');
} else if (typeId === 1) {
// is boolean
const boolToStr = fieldValue ? '1' : '0';
str += boolToStr;
} else {
// is number
const parsedLengths = props.parsedLengths as ParsedLengths;
const parsedLengths = props.pl as ParsedLengths;
str += getNumberIndexString(
parsedLengths,
fieldValue
Expand All @@ -116,7 +137,6 @@ export function getIndexableStringMonad<RxDocType>(
}



declare type ParsedLengths = {
minimum: number;
maximum: number;
Expand Down Expand Up @@ -155,15 +175,14 @@ export function getIndexStringLength<RxDocType>(
const fieldNameProperties = getIndexMeta(schema, index);
let length = 0;
fieldNameProperties.forEach(props => {
const schemaPart = props.schemaPart;
const type = schemaPart.type;
const typeId = props.t;

if (type === 'string') {
length += schemaPart.maxLength as number;
} else if (type === 'boolean') {
if (typeId === 0) {
length += props.ml;
} else if (typeId === 1) {
length += 1;
} else {
const parsedLengths = props.parsedLengths as ParsedLengths;
const parsedLengths = props.pl as ParsedLengths;
length = length + parsedLengths.nonDecimals + parsedLengths.decimals;
}

Expand Down
11 changes: 6 additions & 5 deletions src/plugins/utils/utils-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,27 @@ export type ObjectPathMonadFunction<T, R = any> = (obj: T) => R;
export function objectPathMonad<T, R = any>(objectPath: string): ObjectPathMonadFunction<T, R> {
const split = objectPath.split('.');

// reuse this variable for better performance.
const splitLength = split.length;

/**
* Performance shortcut,
* if no nested path is used,
* directly return the field of the object.
*/
if (split.length === 1) {
if (splitLength === 1) {
return (obj: T) => (obj as any)[objectPath];
}


return (obj: T) => {
let currentVal: any = obj;
let t = 0;
while (t < split.length) {
const subPath = split[t];
for (let i = 0; i < splitLength; ++i) {
const subPath = split[i];
currentVal = currentVal[subPath];
if (typeof currentVal === 'undefined') {
return currentVal;
}
t++;
}
return currentVal;
};
Expand Down

0 comments on commit 8cb2872

Please sign in to comment.