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

Optimize some checkElements* implementations #3123

Merged
merged 5 commits into from
Nov 2, 2023
Merged
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
81 changes: 72 additions & 9 deletions src/webgpu/util/check_contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,28 @@ export function checkElementsEqual(
): ErrorWithExtra | undefined {
assert(actual.constructor === expected.constructor, 'TypedArray type mismatch');
assert(actual.length === expected.length, 'size mismatch');
return checkElementsEqualGenerated(actual, i => expected[i]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmm... I would be interested to see if the change here where instead of creating an additional generator, we are now just comparing the arrays is the real culprit of the speed up? (Sort of the only thing that seems to really have changed functionally?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I measured these changes separately - see the commit messages. The largest speedup was from just splitting the check function in half.


let failedElementsFirstMaybe: number | undefined = undefined;
/** Sparse array with `true` for elements that failed. */
const failedElements: (true | undefined)[] = [];
for (let i = 0; i < actual.length; ++i) {
if (actual[i] !== expected[i]) {
failedElementsFirstMaybe ??= i;
failedElements[i] = true;
}
}

if (failedElementsFirstMaybe === undefined) {
return undefined;
}

const failedElementsFirst = failedElementsFirstMaybe;
return failCheckElements({
actual,
failedElements,
failedElementsFirst,
predicatePrinter: [{ leftHeader: 'expected ==', getValueForCell: index => expected[index] }],
});
}

/**
Expand Down Expand Up @@ -117,11 +138,29 @@ export function checkElementsEqualGenerated(
actual: TypedArrayBufferView,
generator: CheckElementsGenerator
): ErrorWithExtra | undefined {
const error = checkElementsPassPredicate(actual, (index, value) => value === generator(index), {
let failedElementsFirstMaybe: number | undefined = undefined;
/** Sparse array with `true` for elements that failed. */
const failedElements: (true | undefined)[] = [];
for (let i = 0; i < actual.length; ++i) {
if (actual[i] !== generator(i)) {
failedElementsFirstMaybe ??= i;
failedElements[i] = true;
}
}

if (failedElementsFirstMaybe === undefined) {
return undefined;
}

const failedElementsFirst = failedElementsFirstMaybe;
const error = failCheckElements({
actual,
failedElements,
failedElementsFirst,
predicatePrinter: [{ leftHeader: 'expected ==', getValueForCell: index => generator(index) }],
});
// If there was an error, extend it with additional extras.
return error ? new ErrorWithExtra(error, () => ({ generator })) : undefined;
// Add more extras to the error.
return new ErrorWithExtra(error, () => ({ generator }));
}

/**
Expand All @@ -133,14 +172,10 @@ export function checkElementsPassPredicate(
predicate: CheckElementsPredicate,
{ predicatePrinter }: { predicatePrinter?: CheckElementsSupplementalTableRows }
): ErrorWithExtra | undefined {
const size = actual.length;
const ctor = actual.constructor as TypedArrayBufferViewConstructor;
const printAsFloat = ctor === Float16Array || ctor === Float32Array || ctor === Float64Array;

let failedElementsFirstMaybe: number | undefined = undefined;
/** Sparse array with `true` for elements that failed. */
const failedElements: (true | undefined)[] = [];
for (let i = 0; i < size; ++i) {
for (let i = 0; i < actual.length; ++i) {
if (!predicate(i, actual[i])) {
failedElementsFirstMaybe ??= i;
failedElements[i] = true;
Expand All @@ -150,7 +185,35 @@ export function checkElementsPassPredicate(
if (failedElementsFirstMaybe === undefined) {
return undefined;
}

const failedElementsFirst = failedElementsFirstMaybe;
return failCheckElements({ actual, failedElements, failedElementsFirst, predicatePrinter });
}

interface CheckElementsFailOpts {
actual: TypedArrayBufferView;
failedElements: (true | undefined)[];
failedElementsFirst: number;
predicatePrinter?: CheckElementsSupplementalTableRows;
}

/**
* Implements the failure case of some checkElementsX helpers above. This allows those functions to
* implement their checks directly without too many function indirections in between.
*
* Note: Separating this into its own function significantly speeds up the non-error case in
* Chromium (though this may be V8-specific behavior).
*/
function failCheckElements({
actual,
failedElements,
failedElementsFirst,
predicatePrinter,
}: CheckElementsFailOpts): ErrorWithExtra {
const size = actual.length;
const ctor = actual.constructor as TypedArrayBufferViewConstructor;
const printAsFloat = ctor === Float16Array || ctor === Float32Array || ctor === Float64Array;

const failedElementsLast = failedElements.length - 1;

// Include one extra non-failed element at the beginning and end (if they exist), for context.
Expand Down