1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-26 16:09:27 -05:00
denoland-deno/ext/url/01_urlpattern.js
Marcos Casagrande 838140fb82
perf(ext/urlpattern): optimize URLPattern.exec (#20170)
This PR optimizes `URLPattern.exec` 

- Use component keys from constructor instead of calling it on every
`.exec`. AFAIK keys should always be
`protocol`,`username`,`password`,`hostname`,`port`,`pathname`,`search`,`hash`.
Haven't looked much into it but I think it's safe to define these
outside the constructor as well.
- Add a fast path for `/^$/u` (default regexp) and empty input
- Replaced `ArrayPrototypeMap` & `ObjectFromEntries` with a `for` loop.


**this PR**
```
cpu: 13th Gen Intel(R) Core(TM) i9-13900H
runtime: deno 1.36.1 (x86_64-unknown-linux-gnu)

benchmark      time (avg)        iter/s             (min … max)       p75       p99      p995
--------------------------------------------------------------- -----------------------------
exec 1          2.17 µs/iter     461,022.8     (2.14 µs … 2.27 µs)   2.18 µs   2.27 µs   2.27 µs
exec 2          4.13 µs/iter     242,173.4     (4.08 µs … 4.27 µs)   4.15 µs   4.27 µs   4.27 µs
exec 3          2.55 µs/iter     391,508.1     (2.53 µs … 2.68 µs)   2.56 µs   2.68 µs   2.68 µs
```

**main**
```
cpu: 13th Gen Intel(R) Core(TM) i9-13900H
runtime: deno 1.36.1 (x86_64-unknown-linux-gnu)

benchmark      time (avg)        iter/s             (min … max)       p75       p99      p995
--------------------------------------------------------------- -----------------------------
exec 1          2.45 µs/iter     408,092.4     (2.41 µs … 2.55 µs)   2.46 µs   2.55 µs   2.55 µs
exec 2          4.41 µs/iter     226,706.0   (3.49 µs … 399.56 µs)   4.39 µs   5.49 µs   6.07 µs
exec 3          2.99 µs/iter     334,833.4     (2.94 µs … 3.21 µs)   2.99 µs   3.21 µs   3.21 µs
```
2023-08-16 12:58:03 +02:00

265 lines
7.1 KiB
JavaScript

// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// @ts-check
/// <reference path="../../core/internal.d.ts" />
/// <reference path="../../core/lib.deno_core.d.ts" />
/// <reference path="../webidl/internal.d.ts" />
/// <reference path="./internal.d.ts" />
/// <reference path="./lib.deno_url.d.ts" />
const core = globalThis.Deno.core;
const ops = core.ops;
import * as webidl from "ext:deno_webidl/00_webidl.js";
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypePop,
RegExpPrototypeExec,
RegExpPrototypeTest,
SafeRegExp,
Symbol,
SymbolFor,
TypeError,
} = primordials;
const EMPTY_MATCH = [""];
const _components = Symbol("components");
/**
* @typedef Components
* @property {Component} protocol
* @property {Component} username
* @property {Component} password
* @property {Component} hostname
* @property {Component} port
* @property {Component} pathname
* @property {Component} search
* @property {Component} hash
*/
const COMPONENTS_KEYS = [
"protocol",
"username",
"password",
"hostname",
"port",
"pathname",
"search",
"hash",
];
/**
* @typedef Component
* @property {string} patternString
* @property {RegExp} regexp
* @property {string[]} groupNameList
*/
class URLPattern {
/** @type {Components} */
[_components];
/**
* @param {URLPatternInput} input
* @param {string} [baseURL]
*/
constructor(input, baseURL = undefined) {
this[webidl.brand] = webidl.brand;
const prefix = "Failed to construct 'URLPattern'";
webidl.requiredArguments(arguments.length, 1, prefix);
input = webidl.converters.URLPatternInput(input, prefix, "Argument 1");
if (baseURL !== undefined) {
baseURL = webidl.converters.USVString(baseURL, prefix, "Argument 2");
}
const components = ops.op_urlpattern_parse(input, baseURL);
for (let i = 0; i < COMPONENTS_KEYS.length; ++i) {
const key = COMPONENTS_KEYS[i];
try {
components[key].regexp = new SafeRegExp(
components[key].regexpString,
"u",
);
// used for fast path
components[key].matchOnEmptyInput =
components[key].regexpString === "^$";
} catch (e) {
throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`);
}
}
this[_components] = components;
}
get protocol() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].protocol.patternString;
}
get username() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].username.patternString;
}
get password() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].password.patternString;
}
get hostname() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].hostname.patternString;
}
get port() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].port.patternString;
}
get pathname() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].pathname.patternString;
}
get search() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].search.patternString;
}
get hash() {
webidl.assertBranded(this, URLPatternPrototype);
return this[_components].hash.patternString;
}
/**
* @param {URLPatternInput} input
* @param {string} [baseURL]
* @returns {boolean}
*/
test(input, baseURL = undefined) {
webidl.assertBranded(this, URLPatternPrototype);
const prefix = "Failed to execute 'test' on 'URLPattern'";
webidl.requiredArguments(arguments.length, 1, prefix);
input = webidl.converters.URLPatternInput(input, prefix, "Argument 1");
if (baseURL !== undefined) {
baseURL = webidl.converters.USVString(baseURL, prefix, "Argument 2");
}
const res = ops.op_urlpattern_process_match_input(
input,
baseURL,
);
if (res === null) {
return false;
}
const values = res[0];
for (let i = 0; i < COMPONENTS_KEYS.length; ++i) {
const key = COMPONENTS_KEYS[i];
if (!RegExpPrototypeTest(this[_components][key].regexp, values[key])) {
return false;
}
}
return true;
}
/**
* @param {URLPatternInput} input
* @param {string} [baseURL]
* @returns {URLPatternResult | null}
*/
exec(input, baseURL = undefined) {
webidl.assertBranded(this, URLPatternPrototype);
const prefix = "Failed to execute 'exec' on 'URLPattern'";
webidl.requiredArguments(arguments.length, 1, prefix);
input = webidl.converters.URLPatternInput(input, prefix, "Argument 1");
if (baseURL !== undefined) {
baseURL = webidl.converters.USVString(baseURL, prefix, "Argument 2");
}
const res = ops.op_urlpattern_process_match_input(
input,
baseURL,
);
if (res === null) {
return null;
}
const { 0: values, 1: inputs } = res;
if (inputs[1] === null) {
ArrayPrototypePop(inputs);
}
/** @type {URLPatternResult} */
const result = { inputs };
for (let i = 0; i < COMPONENTS_KEYS.length; ++i) {
const key = COMPONENTS_KEYS[i];
/** @type {Component} */
const component = this[_components][key];
const input = values[key];
const match = component.matchOnEmptyInput && input === ""
? EMPTY_MATCH // fast path
: RegExpPrototypeExec(component.regexp, input);
if (match === null) {
return null;
}
const groups = {};
const groupList = component.groupNameList;
for (let i = 0; i < groupList.length; ++i) {
groups[groupList[i]] = match[i + 1] ?? "";
}
result[key] = {
input,
groups,
};
}
return result;
}
[SymbolFor("Deno.customInspect")](inspect) {
return `URLPattern ${
inspect({
protocol: this.protocol,
username: this.username,
password: this.password,
hostname: this.hostname,
port: this.port,
pathname: this.pathname,
search: this.search,
hash: this.hash,
})
}`;
}
}
webidl.configurePrototype(URLPattern);
const URLPatternPrototype = URLPattern.prototype;
webidl.converters.URLPatternInit = webidl
.createDictionaryConverter("URLPatternInit", [
{ key: "protocol", converter: webidl.converters.USVString },
{ key: "username", converter: webidl.converters.USVString },
{ key: "password", converter: webidl.converters.USVString },
{ key: "hostname", converter: webidl.converters.USVString },
{ key: "port", converter: webidl.converters.USVString },
{ key: "pathname", converter: webidl.converters.USVString },
{ key: "search", converter: webidl.converters.USVString },
{ key: "hash", converter: webidl.converters.USVString },
{ key: "baseURL", converter: webidl.converters.USVString },
]);
webidl.converters["URLPatternInput"] = (V, prefix, context, opts) => {
// Union for (URLPatternInit or USVString)
if (typeof V == "object") {
return webidl.converters.URLPatternInit(V, prefix, context, opts);
}
return webidl.converters.USVString(V, prefix, context, opts);
};
export { URLPattern };