-
-
Notifications
You must be signed in to change notification settings - Fork 184
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: perf regression on hot string munging path
This doesn't change any functionality, but it optimizes a few extremely hot code paths for the input typically encountered during a large npm project installation. `String.normalize()` is cached, and trailing-slash removal is done with a single `String.slice()`, rather than multiple slices and `String.length` comparisons. It is extremely rare that any code path is ever hot enough for this kind of thing to be relevant enough to justify this sort of microoptimization, but these two issues resulted in a 25-50% install time increase in some cases, which is fairly dramatic. Fix: npm/cli#3676 PR-URL: #286 Credit: @isaacs Close: #286 Reviewed-by: @wraithgar
- Loading branch information
Showing
6 changed files
with
40 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// warning: extremely hot code path. | ||
// This has been meticulously optimized for use | ||
// within npm install on large package trees. | ||
// Do not edit without careful benchmarking. | ||
const normalizeCache = Object.create(null) | ||
const {hasOwnProperty} = Object.prototype | ||
module.exports = s => { | ||
if (!hasOwnProperty.call(normalizeCache, s)) | ||
normalizeCache[s] = s.normalize('NFKD') | ||
return normalizeCache[s] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,13 @@ | ||
// this is the only approach that was significantly faster than using | ||
// str.replace(/\/+$/, '') for strings ending with a lot of / chars and | ||
// containing multiple / chars. | ||
const batchStrings = [ | ||
'/'.repeat(1024), | ||
'/'.repeat(512), | ||
'/'.repeat(256), | ||
'/'.repeat(128), | ||
'/'.repeat(64), | ||
'/'.repeat(32), | ||
'/'.repeat(16), | ||
'/'.repeat(8), | ||
'/'.repeat(4), | ||
'/'.repeat(2), | ||
'/', | ||
] | ||
|
||
// warning: extremely hot code path. | ||
// This has been meticulously optimized for use | ||
// within npm install on large package trees. | ||
// Do not edit without careful benchmarking. | ||
module.exports = str => { | ||
for (const s of batchStrings) { | ||
while (str.length >= s.length && str.slice(-1 * s.length) === s) | ||
str = str.slice(0, -1 * s.length) | ||
let i = str.length - 1 | ||
let slashesStart = -1 | ||
while (i > -1 && str.charAt(i) === '/') { | ||
slashesStart = i | ||
i-- | ||
} | ||
return str | ||
return slashesStart === -1 ? str : str.slice(0, slashesStart) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
const t = require('tap') | ||
const normalize = require('../lib/normalize-unicode.js') | ||
|
||
// café | ||
const cafe1 = Buffer.from([0x63, 0x61, 0x66, 0xc3, 0xa9]).toString() | ||
|
||
// cafe with a ` | ||
const cafe2 = Buffer.from([0x63, 0x61, 0x66, 0x65, 0xcc, 0x81]).toString() | ||
|
||
t.equal(normalize(cafe1), normalize(cafe2), 'matching unicodes') | ||
t.equal(normalize(cafe1), normalize(cafe2), 'cached') | ||
t.equal(normalize('foo'), 'foo', 'non-unicdoe string') | ||
This comment has been minimized.
Sorry, something went wrong. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 comment
on commit edb8e9a
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.
Fix to security
d'oh, missed that typo. it's only a test name, at least 😬