-
-
Notifications
You must be signed in to change notification settings - Fork 85
additional optimizations #619
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
Conversation
|
Best of 3 with memory info from #620 main branch perf branch further-improvements branch This branch brings the overall performance improvement over
Not bad |
|
Can you rebase the branch on top of the newest main in tailwind-merge? For some reason the diff shows all the changes from #547 which makes it difficult to understand which changes are unique to this PR. Alternatively you can cherry-pick the new commits onto a new branch to fix it. |
Most Tailwind CSS classes have zero or one modifier (e.g., 'p-4' or 'hover:bg-red-500'), making this a very common case. The previous implementation always called sortModifiers() and join(), even when no sorting or joining was needed. This optimization: - Avoids function call overhead for modifiers.length === 0 - Avoids array allocation and join overhead for modifiers.length === 1 - Reduces work for the most common path through the code Benchmark results show ~4.2% improvement on 'collection without cache' benchmark (from 105.39 hz to 109.86 hz).
Refactor getGroupRecursive to use index-based traversal instead of array slicing, and eliminate array mutation from shift(). Previous implementation issues: - classParts.slice(1) created a new array on every recursive call, causing O(n) allocations for deep class name lookups - classParts.shift() mutated the array and moved all elements, causing O(n) element movement and potential V8 deoptimization Optimizations: - Added startIndex parameter to getGroupRecursive() to track position without array slicing - Replaced shift() with index offset calculation, eliminating array mutation - Only slice array when building classRest string for validators (less frequent path), and optimize with early check for startIndex === 0 This maintains monomorphic call sites (important for V8 optimization) while significantly reducing memory allocations during class group lookups. Benchmark results show ~1.6% improvement on 'collection without cache' benchmark (when combined with fast path optimization).
Replace localeCompare() calls with direct string comparison for alphabetical sorting of modifiers. Motivation: - localeCompare() performs locale-aware comparison, which involves: - Locale processing overhead - More complex string comparison logic - Potential locale string allocations - Tailwind CSS modifiers are ASCII identifiers (e.g., 'hover', 'focus', 'dark'), making locale-aware comparison unnecessary - Direct comparison (a < b ? -1 : a > b ? 1 : 0) is simpler and faster, leveraging V8's optimized string comparison primitives This change affects modifier sorting when multiple modifiers need to be sorted alphabetically. For modifier arrays with 2+ elements that require sorting, this provides measurable performance improvement. Benchmark results show ~0.4% improvement on 'collection without cache' benchmark (when combined with previous optimizations).
Pre-compute conflict arrays in Maps at initialization time instead of concatenating arrays at runtime on every call to getConflictingClassGroupIds. Architectural improvement: - Build conflictsWithoutPostfix Map for all class groups with conflicts - Build conflictsWithPostfix Map with pre-merged arrays for classes that have both base conflicts and modifier conflicts - Eliminates runtime concatArrays() calls, replacing with O(1) Map lookups This moves work from the hot path (called for every Tailwind class) to initialization time (called once). The concatArrays operation was creating new arrays and copying elements on every conflict check. Benchmark results show ~2.6% improvement on 'collection without cache' (from 114.02 hz to 116.97 hz).
246804c to
7831c8e
Compare
|
@dcastil all set! |
CodSpeed Performance ReportMerging #619 will not alter performanceComparing Summary
Benchmarks breakdown
|
dcastil
left a comment
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.
Thanks, these look solid! Also many thanks for the detailed commit descriptions, they were quite helpful.
I think we can remove the conflictsWithoutPostfix and the argument to .sort(), but otherwise it all looks good!
Remove conflictsWithoutPostfix and conflictsWithPostfix maps and compute conflicts on-the-fly directly from config objects instead.
Add benchmark for ultra long class lists with many conflicts to demonstrate performance characteristics with large class sets.
|
@dcastil all set! |
The optimization provides no benefit since the function is only called with >1 strings, making the array-based approach unnecessary overhead.
62a3175 to
0799c12
Compare
dcastil
left a comment
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.
Looks good, many thanks again! 🚀
|
This was addressed in release v3.4.0. |
This PR builds on top of #547 with a few additional commits. Each change was verified with best-of-3 benchmark runs and rationale is in the commit body.
Results (all best-of-3)
Main branch baseline
Perf branch
Further optimizations branch