Traverse performance (#10480)

* perf: remove this.inList assignment

* perf: convert NodePath.parentKey into accessor function

* perf: compress shouldSkip/shouldStop/removed traverse flags

* perf: lazy initialize this.skipKeys

* perf: lazily initialize NodePath.data

* add code comments before bit operations

* remove unused typeAnnotation property

# Conflicts:
#	packages/babel-traverse/src/path/index.js

* early return when visitor keys are empty
This commit is contained in:
Huáng Jùnliàng 2019-11-05 05:11:58 -05:00 committed by Nicolò Ribaudo
parent e9c1bce50f
commit b114486bc1
4 changed files with 77 additions and 21 deletions

View File

@ -31,6 +31,10 @@ export default function traverse(
} }
} }
if (!t.VISITOR_KEYS[parent.type]) {
return;
}
visitors.explode(opts); visitors.explode(opts);
traverse.node(parent, opts, scope, state, parentPath); traverse.node(parent, opts, scope, state, parentPath);

View File

@ -1,6 +1,7 @@
// This file contains methods responsible for maintaining a TraversalContext. // This file contains methods responsible for maintaining a TraversalContext.
import traverse from "../index"; import traverse from "../index";
import { SHOULD_SKIP, SHOULD_STOP } from "./index";
export function call(key): boolean { export function call(key): boolean {
const opts = this.opts; const opts = this.opts;
@ -43,7 +44,8 @@ export function _call(fns?: Array<Function>): boolean {
// node has been replaced, it will have been requeued // node has been replaced, it will have been requeued
if (this.node !== node) return true; if (this.node !== node) return true;
if (this.shouldStop || this.shouldSkip || this.removed) return true; // this.shouldSkip || this.shouldStop || this.removed
if (this._traverseFlags > 0) return true;
} }
return false; return false;
@ -97,12 +99,15 @@ export function skip() {
} }
export function skipKey(key) { export function skipKey(key) {
if (this.skipKeys == null) {
this.skipKeys = {};
}
this.skipKeys[key] = true; this.skipKeys[key] = true;
} }
export function stop() { export function stop() {
this.shouldStop = true; // this.shouldSkip = true; this.shouldStop = true;
this.shouldSkip = true; this._traverseFlags |= SHOULD_SKIP | SHOULD_STOP;
} }
export function setScope() { export function setScope() {
@ -122,10 +127,11 @@ export function setScope() {
} }
export function setContext(context) { export function setContext(context) {
this.shouldSkip = false; if (this.skipKeys != null) {
this.shouldStop = false;
this.removed = false;
this.skipKeys = {}; this.skipKeys = {};
}
// this.shouldSkip = false; this.shouldStop = false; this.removed = false;
this._traverseFlags = 0;
if (context) { if (context) {
this.context = context; this.context = context;
@ -220,9 +226,7 @@ export function pushContext(context) {
} }
export function setup(parentPath, container, listKey, key) { export function setup(parentPath, container, listKey, key) {
this.inList = !!listKey;
this.listKey = listKey; this.listKey = listKey;
this.parentKey = listKey || key;
this.container = container; this.container = container;
this.parentPath = parentPath || this.parentPath; this.parentPath = parentPath || this.parentPath;

View File

@ -23,15 +23,18 @@ import * as NodePath_comments from "./comments";
const debug = buildDebug("babel"); const debug = buildDebug("babel");
export const REMOVED = 1 << 0;
export const SHOULD_STOP = 1 << 1;
export const SHOULD_SKIP = 1 << 2;
export default class NodePath { export default class NodePath {
constructor(hub: HubInterface, parent: Object) { constructor(hub: HubInterface, parent: Object) {
this.parent = parent; this.parent = parent;
this.hub = hub; this.hub = hub;
this.contexts = []; this.contexts = [];
this.data = Object.create(null); this.data = null;
this.shouldSkip = false; // this.shouldSkip = false; this.shouldStop = false; this.removed = false;
this.shouldStop = false; this._traverseFlags = 0;
this.removed = false;
this.state = null; this.state = null;
this.opts = null; this.opts = null;
this.skipKeys = null; this.skipKeys = null;
@ -39,13 +42,10 @@ export default class NodePath {
this.context = null; this.context = null;
this.container = null; this.container = null;
this.listKey = null; this.listKey = null;
this.inList = false;
this.parentKey = null;
this.key = null; this.key = null;
this.node = null; this.node = null;
this.scope = null; this.scope = null;
this.type = null; this.type = null;
this.typeAnnotation = null;
} }
parent: Object; parent: Object;
@ -57,18 +57,16 @@ export default class NodePath {
removed: boolean; removed: boolean;
state: any; state: any;
opts: ?Object; opts: ?Object;
_traverseFlags: number;
skipKeys: ?Object; skipKeys: ?Object;
parentPath: ?NodePath; parentPath: ?NodePath;
context: TraversalContext; context: TraversalContext;
container: ?Object | Array<Object>; container: ?Object | Array<Object>;
listKey: ?string; listKey: ?string;
inList: boolean;
parentKey: ?string;
key: ?string; key: ?string;
node: ?Object; node: ?Object;
scope: Scope; scope: Scope;
type: ?string; type: ?string;
typeAnnotation: ?Object;
static get({ hub, parentPath, parent, container, listKey, key }): NodePath { static get({ hub, parentPath, parent, container, listKey, key }): NodePath {
if (!hub && parentPath) { if (!hub && parentPath) {
@ -111,10 +109,16 @@ export default class NodePath {
} }
setData(key: string, val: any): any { setData(key: string, val: any): any {
if (this.data == null) {
this.data = Object.create(null);
}
return (this.data[key] = val); return (this.data[key] = val);
} }
getData(key: string, def?: any): any { getData(key: string, def?: any): any {
if (this.data == null) {
this.data = Object.create(null);
}
let val = this.data[key]; let val = this.data[key];
if (val === undefined && def !== undefined) val = this.data[key] = def; if (val === undefined && def !== undefined) val = this.data[key] = def;
return val; return val;
@ -152,6 +156,49 @@ export default class NodePath {
toString() { toString() {
return generator(this.node).code; return generator(this.node).code;
} }
get inList() {
return !!this.listKey;
}
get parentKey() {
return this.listKey || this.key;
}
get shouldSkip() {
return !!(this._traverseFlags & SHOULD_SKIP);
}
set shouldSkip(v) {
if (v) {
this._traverseFlags |= SHOULD_SKIP;
} else {
this._traverseFlags &= ~SHOULD_SKIP;
}
}
get shouldStop() {
return !!(this._traverseFlags & SHOULD_STOP);
}
set shouldStop(v) {
if (v) {
this._traverseFlags |= SHOULD_STOP;
} else {
this._traverseFlags &= ~SHOULD_STOP;
}
}
get removed() {
return !!(this._traverseFlags & REMOVED);
}
set removed(v) {
if (v) {
this._traverseFlags |= REMOVED;
} else {
this._traverseFlags &= ~REMOVED;
}
}
} }
Object.assign( Object.assign(

View File

@ -1,6 +1,7 @@
// This file contains methods responsible for removing a node. // This file contains methods responsible for removing a node.
import { hooks } from "./lib/removal-hooks"; import { hooks } from "./lib/removal-hooks";
import { REMOVED, SHOULD_SKIP } from "./index";
export function remove() { export function remove() {
this._assertUnremoved(); this._assertUnremoved();
@ -39,8 +40,8 @@ export function _remove() {
} }
export function _markRemoved() { export function _markRemoved() {
this.shouldSkip = true; // this.shouldSkip = true; this.removed = true;
this.removed = true; this._traverseFlags |= SHOULD_SKIP | REMOVED;
this.node = null; this.node = null;
} }