Skip to content

Commit

Permalink
Memoize url.resolve
Browse files Browse the repository at this point in the history
Why:

* Resolve is an expensive API call

This change addresses the need by:

* Cache results of last evaluation.
  • Loading branch information
winkler1 committed Oct 28, 2020
1 parent 7176069 commit 1625472
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 4 deletions.
6 changes: 3 additions & 3 deletions lib/helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

var uri = require('url');
var memoizedResolve = require('./memoizedResolve');

var ValidationError = exports.ValidationError = function ValidationError (message, instance, schema, path, name, argument) {
if(Array.isArray(path)){
Expand Down Expand Up @@ -129,13 +129,13 @@ var SchemaContext = exports.SchemaContext = function SchemaContext (schema, opti
};

SchemaContext.prototype.resolve = function resolve (target) {
return uri.resolve(this.base, target);
return memoizedResolve(this.base, target);
};

SchemaContext.prototype.makeChild = function makeChild(schema, propertyName){
var path = (propertyName===undefined) ? this.path : this.path.concat([propertyName]);
var id = schema.$id || schema.id;
var base = uri.resolve(this.base, id||'');
var base = memoizedResolve(this.base, id||'');
var ctx = new SchemaContext(schema, this.options, path, base, Object.create(this.schemas));
if(id && !ctx.schemas[base]){
ctx.schemas[base] = schema;
Expand Down
20 changes: 20 additions & 0 deletions lib/memoizedResolve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var uri = require('url');

function memoizeLastCall(func) {
var lastArg1 = -1;
var lastArg2 = -1;
var lastResult = null;
return function() {
//return func.apply(null, arguments);
if (lastArg1 === arguments[0] && lastArg2 === arguments[1]) {
return lastResult;
} else {
lastResult = func.apply(null, arguments);
lastArg1 = arguments[0];
lastArg2 = arguments[1];
return lastResult;
}
};
}

module.exports = memoizeLastCall(uri.resolve);
3 changes: 2 additions & 1 deletion lib/validator.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

var memoizedResolve = require('./memoizedResolve');
var urilib = require('url');

var attribute = require('./attribute');
Expand Down Expand Up @@ -115,7 +116,7 @@ Validator.prototype.validate = function validate (instance, schema, options, ctx
// This section indexes subschemas in the provided schema, so they don't need to be added with Validator#addSchema
// This will work so long as the function at uri.resolve() will resolve a relative URI to a relative URI
var id = schema.$id || schema.id;
var base = urilib.resolve(options.base||anonymousBase, id||'');
var base = memoizedResolve(options.base||anonymousBase, id||'');
if(!ctx){
ctx = new SchemaContext(schema, options, [], base, Object.create(this.schemas));
if (!ctx.schemas[base]) {
Expand Down
41 changes: 41 additions & 0 deletions test/Validator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,47 @@ describe('Validator', function () {
assert.deepStrictEqual(validator.unresolvedRefs, ['http://example.com/item.json']);
});
});
describe('Performance test', function(){
function measureTime(iterations, label, arg, fn) {
const startTime = new Date().getTime();
for (let x = 0; x < iterations; ++x) {
fn(arg);
}
const elapsed = new Date().getTime() - startTime;
console.log("Time to do " + iterations + "* " + label + " = " + elapsed+ ", " + elapsed * 1.0 / iterations + " ms/call");
}
it.only('Perf test', function (){
const schema = {
type: "object",
properties: {
field1: { type: "string"},
field2: { type: "string"},
field3: { type: "string"},
field4: { type: "string"},
field5: { type: "string"},
field6: { type: "string"},
field7: { type: "string"},
field8: { type: "string"},
field9: { type: "string"},
field10: { type: "integer"},
field11: { type: "integer"},
field12: { type: "integer"},
field13: { type: "integer"},
field14: { type: "integer"},
field15: { type: "integer"},
field16: { type: "integer"},
field17: { type: "integer"},
field18: { type: "integer"},
field19: { type: "integer"},
field20: { type: "integer"}
}
};
var values = {field1:"a", field2:"a", field3:"a", field4:"a", field5:"a",field6:"a", field7:"a", field8:"a", field9:"a",
field10: 0,field11: 0,field12: 0,field13: 0,field14: 0,field15: 0,field16: 0,field17: 0,field18: 0, field19: 0, field20: 0};

measureTime(10000, "validate", null, function() {return validator.validate(values, schema)});
});
});
describe('Validator#addSchema', function () {
it('argument schema must be a schema: object', function(){
validator.addSchema({}, 'http://example.com/base.json');
Expand Down

0 comments on commit 1625472

Please sign in to comment.