-
Notifications
You must be signed in to change notification settings - Fork 17
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
Increase performance slightly #31
base: master
Are you sure you want to change the base?
Changes from 6 commits
2f19b8b
e07960d
99f2161
7d1b7fb
3cd8393
a27d276
d57a516
d9d9d03
2824746
2968918
2ea3369
2d878cf
6c10697
cd221ce
720817f
30475a4
80aae8f
4d00a1d
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 | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,15 @@ | ||||||
{ | ||||||
"minify": { | ||||||
"mangle": { | ||||||
"properties": { | ||||||
"regex": "/^_/" | ||||||
}, | ||||||
"toplevel": true | ||||||
}, | ||||||
"compress": { | ||||||
"reduce_funcs": false, | ||||||
"passes": 2 | ||||||
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. No real reason to limit passes - Terser will stop processing once two subsequent passes result in no change.
Suggested change
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. Oh this is very interesting to know, thank you again! |
||||||
}, | ||||||
"toplevel": true | ||||||
} | ||||||
} |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,16 @@ | ||
{ | ||
"name": "slow-json-stringify", | ||
"version": "2.0.1", | ||
"description": "The slowest JSON stringifier in the galaxy (:", | ||
"version": "2.1.0", | ||
"description": "The slow.. well actually fastest JSON stringifier in the galaxy.", | ||
"source": "src/sjs.mjs", | ||
"main": "dist/sjs.js", | ||
"module": "dist/sjs.mjs", | ||
"exports": "./dist/sjs.modern.js", | ||
"umd:main": "dist/sjs.umd.js", | ||
"unpkg": "dist/sjs.umd.js", | ||
"types": "index.d.ts", | ||
"scripts": { | ||
"build": "microbundle", | ||
"build": "microbundle --compress --strict", | ||
kurtextrem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"build:dev": "microbundle watch", | ||
"lint": "eslint src/*.mjs test/*.js", | ||
"lint:fix": "eslint src/*.mjs test/*.js --fix", | ||
|
@@ -22,14 +24,14 @@ | |
}, | ||
"devDependencies": { | ||
"benchmark": "^2.1.4", | ||
"chai": "^4.2.0", | ||
"chai": "^4.3.4", | ||
"chai-spies": "^1.0.0", | ||
"eslint": "^5.16.0", | ||
"eslint-config-airbnb-base": "^13.2.0", | ||
"eslint-plugin-import": "^2.18.0", | ||
"fast-json-stringify": "^1.17.0", | ||
"microbundle": "^0.11.0", | ||
"mocha": "^6.1.4" | ||
"eslint": "^7.32.0", | ||
"eslint-config-airbnb-base": "^14.2.1", | ||
"eslint-plugin-import": "^2.24.2", | ||
"fast-json-stringify": "^2.7.9", | ||
"microbundle": "^0.13.3", | ||
"mocha": "^9.1.1" | ||
}, | ||
"keywords": [ | ||
"stringify", | ||
|
@@ -39,12 +41,14 @@ | |
"serialize", | ||
"hash", | ||
"stringification", | ||
"fast" | ||
"fast", | ||
"performance" | ||
], | ||
"author": "Luca Gesmundo <[email protected]>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/lucagez/slow-json-stringify/issues" | ||
}, | ||
"homepage": "https://github.com/lucagez/slow-json-stringify#readme" | ||
"homepage": "https://github.com/lucagez/slow-json-stringify#readme", | ||
"sideeffects": false | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,63 @@ | ||
const _stringRegex = /string/; | ||
|
||
const _replaceString = (type) => | ||
_stringRegex.test(type) ? '"__par__"' : "__par__"; | ||
|
||
const _isLastRegex = /^("}|})/; | ||
|
||
// 3 possibilities after arbitrary property: | ||
// - ", => non-last string property | ||
// - , => non-last non-string property | ||
// - " => last string property | ||
const _matchStartRe = /^(\"\,|\,|\")/; | ||
|
||
/** | ||
* @param {string} str - prepared string already validated. | ||
* @param {array} queue - queue containing the property name to match | ||
* (used for building dynamic regex) needed for the preparation of | ||
* chunks used in different scenarios. | ||
*/ | ||
export default (str, queue) => str | ||
// Matching prepared properties and replacing with target with or without | ||
// double quotes. | ||
// => Avoiding unnecessary concatenation of doublequotes during serialization. | ||
.replace(/"\w+__sjs"/gm, type => (/string/.test(type) ? '"__par__"' : '__par__')) | ||
.split('__par__') | ||
.map((chunk, index, chunks) => { | ||
const _makeChunks = (str, queue) => { | ||
const chunks = str | ||
// Matching prepared properties and replacing with target with or without | ||
// double quotes. | ||
// => Avoiding unnecessary concatenation of doublequotes during serialization. | ||
.replace(/"\w+__sjs"/gm, _replaceString) | ||
.split("__par__"), | ||
result = []; | ||
|
||
for (let i = 0; i < chunks.length; ++i) { | ||
const chunk = chunks[i]; | ||
|
||
// Using dynamic regex to ensure that only the correct property | ||
// at the end of the string it's actually selected. | ||
// => e.g. ,"a":{"a": => ,"a":{ | ||
const matchProp = `("${(queue[index] || {}).name}":(\"?))$`; | ||
const matchWhenLast = `(\,?)${matchProp}`; | ||
const matchProp = `("${queue[i]?.name}":(\"?))$`; | ||
|
||
// Check if current chunk is the last one inside a nested property | ||
const isLast = /^("}|})/.test(chunks[index + 1] || ''); | ||
const isLast = _isLastRegex.test(chunks[i + 1] || ""); | ||
|
||
// If the chunk is the last one the `isUndef` case should match | ||
// the preceding comma too. | ||
const matchPropRe = new RegExp(isLast ? matchWhenLast : matchProp); | ||
|
||
// 3 possibilities after arbitrary property: | ||
// - ", => non-last string property | ||
// - , => non-last non-string property | ||
// - " => last string property | ||
const matchStartRe = /^(\"\,|\,|\")/; | ||
const matchPropRe = new RegExp(isLast ? `(\,?)${matchProp}` : matchProp); | ||
|
||
return { | ||
result.push({ | ||
// notify that the chunk preceding the current one has not | ||
// its corresponding property undefined. | ||
// => This is a V8 optimization as it's way faster writing | ||
// the value of a property than writing the entire property. | ||
flag: false, | ||
pure: chunk, | ||
// Without initial part | ||
prevUndef: chunk.replace(matchStartRe, ''), | ||
prevUndef: chunk.replace(_matchStartRe, ""), | ||
// Without property chars | ||
isUndef: chunk.replace(matchPropRe, ''), | ||
isUndef: chunk.replace(matchPropRe, ""), | ||
// Only remaining chars (can be zero chars) | ||
bothUndef: chunk | ||
.replace(matchStartRe, '') | ||
.replace(matchPropRe, ''), | ||
}; | ||
}); | ||
bothUndef: chunk.replace(_matchStartRe, "").replace(matchPropRe, ""), | ||
}); | ||
} | ||
|
||
return result; | ||
}; | ||
|
||
export { _makeChunks }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,41 @@ | ||
import { _find } from './_utils'; | ||
import { __find, _find } from "./_utils.mjs"; | ||
|
||
const _sjsRegex = /__sjs/; | ||
|
||
function _prepareQueue(originalSchema, queue, obj, acc = []) { | ||
if (_sjsRegex.test(obj)) { | ||
const usedAcc = [...acc]; | ||
const find = __find(usedAcc); | ||
const { serializer } = find(originalSchema); | ||
|
||
queue.push({ | ||
serializer, | ||
find, | ||
name: acc[acc.length - 1], | ||
}); | ||
return; | ||
} | ||
|
||
// Recursively going deeper. | ||
// NOTE: While going deeper, the current prop is pushed into the accumulator | ||
// to keep track of the position inside of the object. | ||
const keys = Object.keys(obj); | ||
for (let i = 0; i < keys.length; ++i) { | ||
const key = keys[i]; | ||
_prepareQueue(originalSchema, queue, obj[key], [...acc, key]); | ||
} | ||
} | ||
|
||
/** | ||
* @param {object} preparedSchema - schema already validated | ||
* with modified prop values to avoid clashes. | ||
* @param {object} originalSchema - User provided schema | ||
* => contains array stringification serializers that are lost during preparation. | ||
*/ | ||
export default (preparedSchema, originalSchema) => { | ||
const _makeQueue = (preparedSchema, originalSchema) => { | ||
const queue = []; | ||
|
||
// Defining a function inside an other function is slow. | ||
// However it's OK for this use case as the queue creation is not time critical. | ||
(function scoped(obj, acc = []) { | ||
if (/__sjs/.test(obj)) { | ||
const usedAcc = Array.from(acc); | ||
const find = _find(usedAcc); | ||
const { serializer } = find(originalSchema); | ||
|
||
queue.push({ | ||
serializer, | ||
find, | ||
name: acc[acc.length - 1], | ||
}); | ||
return; | ||
} | ||
|
||
// Recursively going deeper. | ||
// NOTE: While going deeper, the current prop is pushed into the accumulator | ||
// to keep track of the position inside of the object. | ||
return Object | ||
.keys(obj) | ||
.map(prop => scoped(obj[prop], [...acc, prop])); | ||
})(preparedSchema); | ||
|
||
_prepareQueue(originalSchema, queue, preparedSchema); | ||
return queue; | ||
}; | ||
|
||
export { _makeQueue }; |
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.
toplevel
is true by default.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.
Thank you!