New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reduce memory allocation in rules and utils #2976
base: master
Are you sure you want to change the base?
Changes from 1 commit
b4cd869
152a276
35a88cb
69bcb8d
da9231a
8eff94d
671341e
0f7183e
473d7c1
ccdd17c
a2b5327
d16fcb3
232d6a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -149,20 +149,19 @@ module.exports = { | |||||||||||||||||||
excludeCommas = excludeCommas || false; | ||||||||||||||||||||
|
||||||||||||||||||||
let src = context.getSourceCode().getText(node, node.loc.start.column + extraColumnStart); | ||||||||||||||||||||
const lines = src.split('\n'); | ||||||||||||||||||||
let matches; | ||||||||||||||||||||
if (byLastLine) { | ||||||||||||||||||||
src = lines[lines.length - 1]; | ||||||||||||||||||||
matches = src.match(/.*\n(.*)$/); | ||||||||||||||||||||
} else { | ||||||||||||||||||||
src = lines[0]; | ||||||||||||||||||||
matches = src.match(/^(.*)\n/); | ||||||||||||||||||||
} | ||||||||||||||||||||
Comment on lines
+152
to
157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||
|
||||||||||||||||||||
const skip = excludeCommas ? ',' : ''; | ||||||||||||||||||||
src = matches ? matches[1] : src; | ||||||||||||||||||||
|
||||||||||||||||||||
let regExp; | ||||||||||||||||||||
if (indentType === 'space') { | ||||||||||||||||||||
regExp = new RegExp(`^[ ${skip}]+`); | ||||||||||||||||||||
regExp = excludeCommas ? /^[ ,]+/ : /^[ ]+/; | ||||||||||||||||||||
} else { | ||||||||||||||||||||
regExp = new RegExp(`^[\t${skip}]+`); | ||||||||||||||||||||
regExp = excludeCommas ? /^[\t,]+/ : /^[\t]+/; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
const indent = regExp.exec(src); | ||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ function isJSXText(node) { | |
* @returns {boolean} | ||
*/ | ||
function isOnlyWhitespace(text) { | ||
return text.trim().length === 0; | ||
return !/\S/.test(text); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these aren't necessarily always the same thing; Additionally, is this better than There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point - it's hard to know for sure if this regex is truly equivalent to trim - a cursory glance at the spec says it removes "WhiteSpace and LineTerminator.". The nice thing is it doesn't involve creating a new string, and will finish early the moment it finds a non-whitespace character, rather than continuing on trying to remove all the whitespace. In terms of clarity I'm not sure - I'm happy to go with your suggestion here, but is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess I'm not convinced that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry about the late reply! I was really convinced a regex would be faster because there'd be less work (stop the moment non-whitespace is found, rather than doing the work of constructing a new string, then checking its length), but after constructing some basic micro benchmarks to check it, they're basically equivalent in speed, and in Chrome 91 + latest V8 sometimes trim was faster. Thanks for pushing back on this, I'm glad it was double checked - will revert this change. |
||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -35,7 +35,7 @@ function generateErrorMessageWithParentName(parentName) { | |||||||||||||||||
* @returns {Boolean} | ||||||||||||||||||
*/ | ||||||||||||||||||
function startsWithRender(text) { | ||||||||||||||||||
return (text || '').startsWith('render'); | ||||||||||||||||||
return text !== undefined && text.startsWith('render'); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good spot - if it's a false-y string, it won't start with 'render' anyway. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm specifically interested in changes related to this rule. Thanks for testing the performance @willheslam! While this improvement is definitely welcome I'm having a hard time believing it actually speeds up this rule by ~40%.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point - the performance gains for There are lots of other optimisations inside lib/util/Components.js that are contributing to this speed up too - to prove this is the case, I tried running There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, that makes perfect sense. Those utilities are perfect spot for optimization as they are being used widely across the code base. |
||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/** | ||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -27,7 +27,12 @@ function usedPropTypesAreEquivalent(propA, propB) { | |||||||||||||||||||||||||||||
if (!propA.allNames && !propB.allNames) { | ||||||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
if (Array.isArray(propA.allNames) && Array.isArray(propB.allNames) && propA.allNames.join('') === propB.allNames.join('')) { | ||||||||||||||||||||||||||||||
if (Array.isArray(propA.allNames) | ||||||||||||||||||||||||||||||
&& Array.isArray(propB.allNames) | ||||||||||||||||||||||||||||||
&& ( | ||||||||||||||||||||||||||||||
(propA.allNames === propB.allNames) | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when will this check ever actually be true? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From my testing, most of the time. Please check my logic below: With this basic benchmarking on a project with about 550 jsx files:
With an otherwise unaltered lib/util/Components.js,
but this settled down to about 2 to 1 after the condition had been checked almost a million times.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess I'm surprised that the AST parser would generate two memoized |
||||||||||||||||||||||||||||||
|| propA.allNames.join('') === propB.allNames.join('') | ||||||||||||||||||||||||||||||
)) { | ||||||||||||||||||||||||||||||
Comment on lines
+30
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||
|
@@ -235,6 +240,10 @@ function componentRule(rule, context) { | |||||||||||||||||||||||||||||
const components = new Components(); | ||||||||||||||||||||||||||||||
const wrapperFunctions = getWrapperFunctions(context, pragma); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
let es5ComponentRegex; | ||||||||||||||||||||||||||||||
let es6ComponentRegex; | ||||||||||||||||||||||||||||||
let pureComponentRegex; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Utilities for component detection | ||||||||||||||||||||||||||||||
const utils = { | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
@@ -248,7 +257,8 @@ function componentRule(rule, context) { | |||||||||||||||||||||||||||||
if (!node.parent) { | ||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
return new RegExp(`^(${pragma}\\.)?${createClass}$`).test(sourceCode.getText(node.parent.callee)); | ||||||||||||||||||||||||||||||
es5ComponentRegex = es5ComponentRegex || new RegExp(`^(${pragma}\\.)?${createClass}$`); | ||||||||||||||||||||||||||||||
return es5ComponentRegex.test(sourceCode.getText(node.parent.callee)); | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||
|
@@ -265,7 +275,8 @@ function componentRule(rule, context) { | |||||||||||||||||||||||||||||
if (!node.superClass) { | ||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
return new RegExp(`^(${pragma}\\.)?(Pure)?Component$`).test(sourceCode.getText(node.superClass)); | ||||||||||||||||||||||||||||||
es6ComponentRegex = es6ComponentRegex || new RegExp(`^(${pragma}\\.)?(Pure)?Component$`); | ||||||||||||||||||||||||||||||
return es6ComponentRegex.test(sourceCode.getText(node.superClass)); | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||
|
@@ -308,7 +319,8 @@ function componentRule(rule, context) { | |||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||
isPureComponent(node) { | ||||||||||||||||||||||||||||||
if (node.superClass) { | ||||||||||||||||||||||||||||||
return new RegExp(`^(${pragma}\\.)?PureComponent$`).test(sourceCode.getText(node.superClass)); | ||||||||||||||||||||||||||||||
pureComponentRegex = pureComponentRegex || new RegExp(`^(${pragma}\\.)?PureComponent$`); | ||||||||||||||||||||||||||||||
return pureComponentRegex.test(sourceCode.getText(node.superClass)); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we make this regex at module level, rather than nesting it deeper? (same with the useOperator regexes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch - this actually resulted in my learning that, on top of regex literals being faster than dynamic (
new RegExp(string)
) ones, in ES5, regex literal instances have their own identity, which means caching them at the module level is faster yet again:https://stackoverflow.com/questions/9750338/dynamic-vs-inline-regexp-performance-in-javascript/32523333
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm relatively sure that's not unique to ES5, that's eternally been the semantics in JS.