Skip to content
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

perf(vite): rework endsWith to direct index #24746

Merged
merged 9 commits into from Jan 29, 2024

Conversation

GalacticHypernova
Copy link
Contributor

@GalacticHypernova GalacticHypernova commented Dec 14, 2023

πŸ”— Linked issue

nuxt/framework#8529

❓ Type of change

  • πŸ“– Documentation (updates to the documentation, readme or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

Similar to #24744, endsWith also has a performance downside when checking single characters. This reworks it to direct index in strings, which inccreases performance by ~40%.
Benchmark results:

const nuxt={
    _ignorePatterns:["abcdefghijklmnopqrstuvwxyz*!","abcdefghijklmnopqrstuvwxyz*!","abcdefghijklmnopqrstuvwxyz*!","abcdefghijklmnopqrstuvwxyz*!","abcdefghijklmnopqrstuvwxyz*!","abcdefghijklmnopqrstuvwxyz*!","abcdefghijklmnopqrstuvwxyz*!","abcdefghijklmnopqrstuvwxyz*!"]
}
console.time("currentSingle")
for (let index = 0; index < 100000; index++) {
    nuxt._ignorePatterns.map(p => p.endsWith('*'))
}
console.timeEnd("currentSingle")
console.time("PRSingle")
for (let index = 0; index < 100000; index++) {
    nuxt._ignorePatterns.map(p=>p[p.length-1]==="*")
}
console.timeEnd("PRSingle")
    console.time("currentM")
for (let index = 0; index < 100000; index++) {
    nuxt._ignorePatterns.map(p => p.endsWith('!*'))
}
console.timeEnd("currentM")
console.time("PRM")
for (let index = 0; index < 100000; index++) {
    nuxt._ignorePatterns.map(p => p[p.length-2] === '!' && p[p.length-1] === '*')
}
console.timeEnd("PRM")

image

And yes, here too we can make a tradeoff of partial readability for performance if needed.

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

Copy link

stackblitz bot commented Dec 14, 2023

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@github-actions github-actions bot added the 3.x label Dec 14, 2023
@@ -116,7 +116,8 @@ export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptio
}

// TODO: Optimize me (https://github.com/nuxt/framework/pull/8529)
const endsWithComma = code.slice(codeIndex + (node as any).start, codeIndex + (node as any).end - 1).trim().endsWith(',')
const newCode = code.slice(codeIndex + (node as any).start, codeIndex + (node as any).end - 1).trim()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielroe this entire slice/trim operation, is it even needed? Can we not just do this?

code[codeIndex + (node as any).end - 1]===','

I saw that pi left a comment there, is there a possibility codeIndex + (node as any).end - 1 would point to a whitespace?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I think it's possible it will end with whitespace...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'll try to run some tests with this. There's gotta be a way to make it work without it..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could iterate backwards in a while loop... πŸ€”

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be possible, it will at the very least reduce the amount of characters "digested" by trim I suppose which runs both ways (will make some benchmarks for this), but I wonder if there's a way to make it even faster, like if the expression itself will not contain whitespace, in which case last index could be used directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

For starter optimization, we can use trimEnd which increase performance by ~50%

@GalacticHypernova GalacticHypernova changed the title perf(vite): rework endsWith to direct index checking perf(vite): rework endsWith to direct index Dec 14, 2023
@danielroe danielroe marked this pull request as draft December 14, 2023 11:04
@GalacticHypernova
Copy link
Contributor Author

@danielroe would you like me to also fix this in tests? Or keep this in packages only?

@danielroe
Copy link
Member

We can ignore tests. πŸ‘Œ

@GalacticHypernova
Copy link
Contributor Author

πŸ‘

@GalacticHypernova
Copy link
Contributor Author

GalacticHypernova commented Dec 17, 2023

I'm getting some odd results and even odder deviations in my analysis. We should count on offline benchmarks, right? Ones made on a PC rather than on the browser?
Because I find it impossible that one of the benchmarks suggested checking a single character with a simple index is ~33% slower than with endsWith

@danielroe
Copy link
Member

Benchmarks should be on node and bun, for changes to kit/schema/nuxt build.

I would take perf optimisations with an extreme grain of salt as runtimes frequently optimise newer features in future... In general avoiding sweeping statements.

Ideally reproducible benchmarks should be provided - might be worth using something like codspeed, and adding a comment to remove when things change.

@GalacticHypernova
Copy link
Contributor Author

GalacticHypernova commented Dec 17, 2023

I had a feeling it was just the online benchmarks faking because on node it is marginally faster.

might be worth using something like codspeed, and adding a comment to remove when things change.

I can do that, but considering endsWith was implemented in ECMA6 and hasn't been optimized since I can say pretty confidently this isn't likely to change anytime soon.

@GalacticHypernova GalacticHypernova marked this pull request as ready for review January 23, 2024 16:15
@GalacticHypernova
Copy link
Contributor Author

Well, a little underwhelming that there was only one thing to change that actually makes any sort of difference, but I guess this happens =)

@danielroe danielroe merged commit 860cfe1 into nuxt:main Jan 29, 2024
33 of 34 checks passed
@github-actions github-actions bot mentioned this pull request Jan 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants