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

feat(type) add some options to the JIT compiler #546

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

M-jerez
Copy link
Contributor

@M-jerez M-jerez commented Feb 1, 2024

Summary of changes

  • Add options to configure the JIT compiler.
  • 2 small optimisations to emitted code (won't make any noticeable difference, mostly emitted code is more concise) here & here (these ones affect existing serializer also)

After a bit of investigation about how to possibly improve the validation and serialization performance, there were a couple of improvements that could be easy enough to tackle:

  • No emitting errors: only return success or fail (this removes all error related code from the generated JIT functions)
  • No onpopulate properties: this are handier when we know the data and can consider it safe (i.e: orm operations) but not so much when the data is unsafe (i.e: user input), there is an extra check for unpopulated properties for every single property, so removing those from the function also improve performance.
  • No groups: controls wether or not the code related to groups functionality is emitted

The PR might look big but is actually not that big and kind of simple. I just went for the low hanging fruit.

How to use

Data:

interface ToBeChecked {
  number: number;
  negNumber: number;
  maxNumber: number;
  string: string;
  longString: string;
  boolean: boolean;
  deeplyNested: {
    foo: string;
    num: number;
    bool: boolean;
  };
}

IE: Normal Serializer

import { castFunction, getValidatorFunction } from '@deepkit/type';

const isToBeChecked = getValidatorFunction<ToBeChecked>();
const safeToBeChecked = castFunction<ToBeChecked>();

export function assertLoose(input: unknown): boolean {
  if (!isToBeChecked(input) as boolean) throw new Error('wrong type.');
  return true;
}

export function parseSafe(input: unknown): ToBeChecked {
  return safeToBeChecked(input);
}

IE: Quick Serializer (no Emit errors , no unpopulated check, no groups)
NOTE THESE FUNCTIONS DO NOT EMIT ERROR INFORMATION WHEN VALIDATING

import {
  castFunction,
  getValidatorFunction,
  quickSerializer,
} from '@deepkit/type';

const isToBeChecked = getValidatorFunction<ToBeChecked>(quickSerializer);
const safeToBeChecked = castFunction<ToBeChecked>(quickSerializer);

export function assertLoose(input: unknown): boolean {
  if (!isToBeChecked(input) as boolean) throw new Error('wrong type.');
  return true;
}

export function parseSafe(input: unknown): ToBeChecked {
  return safeToBeChecked(input);
}

Benchmark results

to test the benchmarks download this branch install modules and simLink @deepkit/types into it, then run the benhcmarks.

benhcmark-after-pr-changes

Would say the Improvement when serializing is not really significant but it is when validating!

Benchmark results from master branch

before-pr-changes

COMPARISON of generated JIT code

REGULAR JIT VALIDATION FUNCTION

// Validation function generated using the regular serializer
function self(data, state, _path) {
  "use strict";

  var result;
  if (_path === undefined) _path = "";

  state = state ? state : {};

  result = true;
  if (data && "object" === typeof data) {
    if (data["number"] !== unpopulatedSymbol) {
      let check_0 = "number" === typeof data["number"];
      if (!check_0)
        if (state.errors)
          state.errors.push(
            new ValidationErrorItem(
              _path + "." + "number",
              "type",
              "Not a number",
              data["number"]
            )
          );
      if (!check_0) result = false;
    }
    if (data["negNumber"] !== unpopulatedSymbol) {
      let check_1 = "number" === typeof data["negNumber"];
      if (!check_1)
        if (state.errors)
          state.errors.push(
            new ValidationErrorItem(
              _path + "." + "negNumber",
              "type",
              "Not a number",
              data["negNumber"]
            )
          );
      if (!check_1) result = false;
    }
    if (data["maxNumber"] !== unpopulatedSymbol) {
      let check_2 = "number" === typeof data["maxNumber"];
      if (!check_2)
        if (state.errors)
          state.errors.push(
            new ValidationErrorItem(
              _path + "." + "maxNumber",
              "type",
              "Not a number",
              data["maxNumber"]
            )
          );
      if (!check_2) result = false;
    }
    if (data["string"] !== unpopulatedSymbol) {
      let check_3 = "string" === typeof data["string"];
      if (!check_3)
        if (state.errors)
          state.errors.push(
            new ValidationErrorItem(
              _path + "." + "string",
              "type",
              "Not a string",
              data["string"]
            )
          );
      if (!check_3) result = false;
    }
    if (data["longString"] !== unpopulatedSymbol) {
      let check_4 = "string" === typeof data["longString"];
      if (!check_4)
        if (state.errors)
          state.errors.push(
            new ValidationErrorItem(
              _path + "." + "longString",
              "type",
              "Not a string",
              data["longString"]
            )
          );
      if (!check_4) result = false;
    }
    if (data["boolean"] !== unpopulatedSymbol) {
      let check_5 = "boolean" === typeof data["boolean"];
      if (!check_5)
        if (state.errors)
          state.errors.push(
            new ValidationErrorItem(
              _path + "." + "boolean",
              "type",
              "Not a boolean",
              data["boolean"]
            )
          );
      if (!check_5) result = false;
    }
    if (data["deeplyNested"] !== unpopulatedSymbol) {
      let check_6 = false; //call jit for check_6 via propertyName
      check_6 = jit_1.fn(
        data["deeplyNested"],
        state,
        _path + "." + "deeplyNested"
      );
      if (!check_6) result = false;
    }
  } else {
    if (true)
      if (state.errors)
        state.errors.push(
          new ValidationErrorItem(_path, "type", "Not an object", data)
        );
    result = false;
  }

  return result;
}

QUICK VALIDATION FUNCTION

// Validation function generated using the quick serializer
function self(data, state, _path) {
  "use strict";

  var result;
  if (_path === undefined) _path = "";

  state = state ? state : {};

  result = true;
  if (data && "object" === typeof data) {
    let check_0 = "number" === typeof data["number"];
    if (!check_0) result = false;

    let check_1 = "number" === typeof data["negNumber"];
    if (!check_1) result = false;

    let check_2 = "number" === typeof data["maxNumber"];
    if (!check_2) result = false;

    let check_3 = "string" === typeof data["string"];
    if (!check_3) result = false;

    let check_4 = "string" === typeof data["longString"];
    if (!check_4) result = false;

    let check_5 = "boolean" === typeof data["boolean"];
    if (!check_5) result = false;

    let check_6 = false; //call jit for check_6 via propertyName
    check_6 = jit_1.fn(
      data["deeplyNested"],
      state,
      _path + "." + "deeplyNested"
    );
    if (!check_6) result = false;
  } else {
    if (true) {
      /* noop */
    }
    result = false;
  }

  return result;
}

Any comments please let me know! 👍

Relinquishment of Rights

Please mark following checkbox to confirm that you relinquish all rights of your changes:

  • I waive and relinquish all rights regarding this changes (including code, text, and images) to Deepkit UG (limited), Germany. This changes (including code, text, and images) are under MIT license without name attribution, copyright notice, and permission notice requirement.

…nd `unpopulated` checks, this should improve JIT compiler performance
…er.spec & typeguard.spec with default and quick serialiser
…o solve property name problem With BsonSerializer
packages/type/tests/serializer.spec.ts Outdated Show resolved Hide resolved
@M-jerez M-jerez marked this pull request as ready for review February 2, 2024 02:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants