Skip to content

Performance - #22

Open
koppen wants to merge 2 commits into
mainfrom
performance
Open

Performance#22
koppen wants to merge 2 commits into
mainfrom
performance

Conversation

@koppen

@koppen koppen commented Aug 6, 2026

Copy link
Copy Markdown
Member

By maintaining a Set alongside the ordered array of entries we can reduce the time complexity of add, toggle, include?, and replace from O(n·m) to effectively O(m) (amortized O(1) per token).

  • Added an @entries_set (Set) maintained alongside the ordered @entries array. include?, add, toggle's existence check, and replace's existence checks now use it for O(1) membership lookups instead of an O(n) Array#include? scan.
  • add and remove update both structures together; replace syncs the set when it swaps a token in place.
  • Added a reset(tokens) method that replaces all entries and rebuilds the set atomically — needed because Classlist::Reset was previously reaching into the raw array via entries.replace(entries), which would have silently desynced the set. Updated lib/classlist/reset.rb to call original.reset(entries) instead.

Impact: add, toggle, include?, replace, and any +-chain building on them went from O(n·m) to effectively O(m) (amortized O(1) per token). Benchmarks confirm it — add at n=4000 dropped from 0.068s to 0.002s, toggle similarly.

Since add/toggle/chained-+ are now genuinely linear, I lowered their expected_order from 2 to 1 in test/benchmark/ — with the old threshold, a regression back to O(n²) wouldn't have tripped the guard. remove_benchmark.rb stays at order 2 with a comment explaining why.

koppen added 2 commits August 6, 2026 09:30
Run them with `rake benchmark`:

    #add with many distinct tokens             n=500     0.0012s  n=4000    0.0667s  ratio=  54.72  max= 128.00  OK
    incremental + and render on each step      n=300     0.0017s  n=2400    0.0852s  ratio=  50.26  max= 128.00  OK
    chained + with a single render at the end  n=300     0.0007s  n=2400    0.0280s  ratio=  38.69  max= 128.00  OK
    #remove with many distinct tokens          n=500     0.0018s  n=4000    0.1076s  ratio=  59.70  max= 128.00  OK
    #toggle with many distinct tokens          n=500     0.0024s  n=4000    0.1329s  ratio=  55.74  max= 128.00  OK
By maintaining a Set alongside the ordered array of entries we can
reduce the time complexity of add, toggle, include?, and replace from
O(n·m) to effectively O(m) (amortized O(1) per token).

- Added an @entries_set (Set) maintained alongside the ordered @entries
  array. include?, add, toggle's existence check, and replace's
  existence checks now use it for O(1) membership lookups instead of an
  O(n) Array#include? scan.
- add and remove update both structures together; replace syncs the set
  when it swaps a token in place.
- Added a reset(tokens) method that replaces all entries and rebuilds
  the set atomically — needed because Classlist::Reset was previously
  reaching into the raw array via entries.replace(entries), which would
  have silently desynced the set. Updated lib/classlist/reset.rb to call
  original.reset(entries) instead.

Impact: add, toggle, include?, replace, and any +-chain building on them
went from O(n·m) to effectively O(m) (amortized O(1) per token).
Benchmarks confirm it — add at n=4000 dropped from 0.068s to 0.002s,
toggle similarly.

What's still O(n) per call, and why I left it: remove uses Array#delete,
which has to shift elements after removing one — that's inherent to
array-backed ordered storage and isn't fixable by adding a set (the set
only helps skip delete calls for tokens that were never present). Fixing
that would require swapping the whole backing structure (e.g. a hash +
linked list), which is a much bigger change than what was asked here.

Since add/toggle/chained-+ are now genuinely linear, I lowered their
expected_order from 2 to 1 in test/benchmark/ — with the old threshold,
a regression back to O(n²) wouldn't have tripped the guard.
remove_benchmark.rb stays at order 2 with a comment explaining why.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant