1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-29 16:30:56 -05:00

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
```
This commit is contained in:
Marcos Casagrande 2023-08-16 12:58:03 +02:00 committed by Divy Srivastava
parent 08b9f920f6
commit 75ea2c1b20

View file

@ -12,10 +12,7 @@ const ops = core.ops;
import * as webidl from "ext:deno_webidl/00_webidl.js"; import * as webidl from "ext:deno_webidl/00_webidl.js";
const primordials = globalThis.__bootstrap.primordials; const primordials = globalThis.__bootstrap.primordials;
const { const {
ArrayPrototypeMap,
ArrayPrototypePop, ArrayPrototypePop,
ObjectFromEntries,
ObjectKeys,
RegExpPrototypeExec, RegExpPrototypeExec,
RegExpPrototypeTest, RegExpPrototypeTest,
SafeRegExp, SafeRegExp,
@ -24,6 +21,7 @@ const {
TypeError, TypeError,
} = primordials; } = primordials;
const EMPTY_MATCH = [""];
const _components = Symbol("components"); const _components = Symbol("components");
/** /**
@ -37,6 +35,16 @@ const _components = Symbol("components");
* @property {Component} search * @property {Component} search
* @property {Component} hash * @property {Component} hash
*/ */
const COMPONENTS_KEYS = [
"protocol",
"username",
"password",
"hostname",
"port",
"pathname",
"search",
"hash",
];
/** /**
* @typedef Component * @typedef Component
@ -64,19 +72,20 @@ class URLPattern {
const components = ops.op_urlpattern_parse(input, baseURL); const components = ops.op_urlpattern_parse(input, baseURL);
const keys = ObjectKeys(components); for (let i = 0; i < COMPONENTS_KEYS.length; ++i) {
for (let i = 0; i < keys.length; ++i) { const key = COMPONENTS_KEYS[i];
const key = keys[i];
try { try {
components[key].regexp = new SafeRegExp( components[key].regexp = new SafeRegExp(
components[key].regexpString, components[key].regexpString,
"u", "u",
); );
// used for fast path
components[key].matchOnEmptyInput =
components[key].regexpString === "^$";
} catch (e) { } catch (e) {
throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`); throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`);
} }
} }
this[_components] = components; this[_components] = components;
} }
@ -144,9 +153,8 @@ class URLPattern {
const values = res[0]; const values = res[0];
const keys = ObjectKeys(values); for (let i = 0; i < COMPONENTS_KEYS.length; ++i) {
for (let i = 0; i < keys.length; ++i) { const key = COMPONENTS_KEYS[i];
const key = keys[i];
if (!RegExpPrototypeTest(this[_components][key].regexp, values[key])) { if (!RegExpPrototypeTest(this[_components][key].regexp, values[key])) {
return false; return false;
} }
@ -185,21 +193,26 @@ class URLPattern {
/** @type {URLPatternResult} */ /** @type {URLPatternResult} */
const result = { inputs }; const result = { inputs };
const keys = ObjectKeys(values); for (let i = 0; i < COMPONENTS_KEYS.length; ++i) {
for (let i = 0; i < keys.length; ++i) { const key = COMPONENTS_KEYS[i];
const key = keys[i];
/** @type {Component} */ /** @type {Component} */
const component = this[_components][key]; const component = this[_components][key];
const input = values[key]; const input = values[key];
const match = RegExpPrototypeExec(component.regexp, input);
const match = component.matchOnEmptyInput && input === ""
? EMPTY_MATCH // fast path
: RegExpPrototypeExec(component.regexp, input);
if (match === null) { if (match === null) {
return null; return null;
} }
const groupEntries = ArrayPrototypeMap(
component.groupNameList, const groups = {};
(name, i) => [name, match[i + 1] ?? ""], const groupList = component.groupNameList;
); for (let i = 0; i < groupList.length; ++i) {
const groups = ObjectFromEntries(groupEntries); groups[groupList[i]] = match[i + 1] ?? "";
}
result[key] = { result[key] = {
input, input,
groups, groups,