Skip to content

Reduce code size by optionally refactoring errors into a function #1098

@fabiospampinato

Description

@fabiospampinato

I'm using ajv in a library for managing settings, and while optimizing it I think I discovered a few potentially good ideas for optimizing ajv's initialization time.

ajv is plenty fast when validating, but in order to get there one has to first instantiate it and compile the schema, which are not particularly fast operations.

Lazy loading dependencies

It takes about ~30ms to require ajv on my machine, that's not too bad but I think it could be brought down further by lazy loading dependencies once they are actually about to be used.

Disabling schema caching and serialization

There's no standalone option for disabling schema caching and serialization, currently one has to set the following options, which are a bit tedious to write (and perhaps kind of difficult to come up with):

{
  serialize: false,
  cache: {
    put: () => {},
    get: () => {},
    del: () => {},
    clear: () => {}
  }
}

I think there are some very common use cases where schema caching is just not useful, for example if I only have a single schema, or if I never re-compile the same schemas, then this is just:

  1. wasting time serializing objects.
  2. wasting time importing fast-json-stable-stringify without using it (under some scenarios at least).
    • I'm also not sure fast-json-stable-stringify is needed, JSON.stringify is faster, and although the order of object properties is not guaranteed in JS in practice V8 keeps them ordered, I think the other major engines probably do the same, and they aren't going to change this for backwards compatibility.
  3. wasting memory storing the string representation of our schemas, plus potentially older compiled schemas no longer in use.

I think a standalone option for disabling schema caching entirely, and a mention in the readme that a slight performance and memory gain can be achieved by disabling it, would be a good idea.

Shorter validation functions

This is perhaps the biggest optimization opportunity I found.

The generated validation function is too long and grows too fast. I've attached a sample source code generated by compiling a very simple schema with just about 100 properties in it, it weighs 40kb!

As soon as the schema becomes more complicated, and especially if more properties are added, the generated source code may very well weigh 1mb or more. All this source code wastes memory and it needs to be parsed and that's not free, especially on mobile.

Most of the source code is actually about generating error objects and updating the related counters for each validation block. I think this issue should be addressed like so:

  • when it.level === 0 an helper function for generating the error object for that specific validation function should be outputted, then it should be called in the rest of the file when generating the error objects.
    • Most of the validations pass and validation is usually stopped as soon as the first error is found, so the extra function call here won't have any even remotely meaningful cost performance-wise, perhaps the engine might even inline it itself.

I think the generated source code under my example scenario could weigh about 15kb rather than 40kb just by implementing this, a reduction of about 60%.

source.js


I hope these suggestions are useful, they might not sound like much under some scenarios, but I think they can make the difference under others.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions