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

Micro-optimize search #482

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open

Conversation

trotzig
Copy link

@trotzig trotzig commented Jan 13, 2017

This PR started out with me exploring whether to move some of the functions used when searching over to a web worker, but ended up just making a few minor improvements to the matching behavior. I call it "micro-optimize" because the DOM manipulation is still the most expensive part of list.js searching.

With newer versions of npm, the node_modules folder is flattened.
Instead of reaching into grunt-mocha/node_modules to find mocha.css and
mocha.js, we need to look directly in the mocha folder.
By doing this, we make `setSearchString` twice as fast. It's already
pretty fast, but this won't hurt.
By memoizing a RegExp instead of matching against a string, the total
time to search goes down with about 30-60%.

This is the total search times for strings "l" and "le" on a list of
1000 items (most items named "Leia <n>", where <n> is a incremented
number).

Without RegExp memoization:

  first run: 3.2ms
  second run: 1.63ms

With RegExp memoization:

  first run: 2.16ms
  second run: 0.7ms
Instead of converting to lower-case before we match a string, we can
tell the regexp to do this for us. This makes searching a little bit
faster, roughly 25% in my local testing.
Since we don't care about where in the string the match was, we can use
RegExp.test directly (which returns a boolean). This might make
searching a little faster, although I didn't see any significant signs
of that in my local testing.
search.reset();
} else {
list.searched = true;
if (customSearch) {
customSearch(searchString, columns);
customSearch(searchPattern, columns);
Copy link
Author

Choose a reason for hiding this comment

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

This might be a breaking change, depending on how people use the customSearch option.

Copy link
Contributor

Choose a reason for hiding this comment

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

searchPattern.source could help.

By using a range, we can improve performance of templater.clear() by
roughly 25% (tested in Chrome). I decided to make this perf enhancement
opt-in to browsers supporting the `document.createRange` method. It's
not supported in IE<9, but most modern browsers should have it.

On a list with 1000 items, this change took down the time it takes for
templater.clear() from roughly 40ms to <30ms. In total, the whole
search execution takes about 160ms, so this shaves off ~6% of execution
time.
By adding DOM nodes one by one, we're causing multiple reflows in the
browser. This can have bad effects on performane when lists are longs.
By adding DOM nodes to a fragment we can limit the number of reflows to
one. This cuts execution time for list.update() in half (40ms to 20ms)
on a list with 1000 items.

While I was in the code, I noticed a misspelled comment that I fixed.
src/search.js Outdated
prepare.setOptions(arguments); // str, cols|searchFunction, searchFunction
prepare.setColumns();

if (searchString === "" ) {
if (!searchPattern ) {

Choose a reason for hiding this comment

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

searchPattern will never be null:

console.log(new RegExp());
// output: /(?:)/

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.

3 participants