5

I have a really strange effect in a web app I wrote. The number of current search results has a huge impact on the speed of the next search, even though the search does in no way use the result list.

I'm not sure how much code I should post here to demonstrate what the app does, but the whole app is online at http://connective-lex.info. (To see the effect, select all lexicons, then vary the search options, e.g., check one or none.)

This is the query function. The first line, marked !!!, clears the previous result list, resulting in really fast searches. If this line is commented out, the search starts a little faster, but then is extremely slow if many items are in the previous result list.

The result list is this.results, and isn't used in the search code. It also isn't used in the ResultsFilter class which does the actual filtering, which I omit for brevity but will happily post more code if you need more.

  // File: c24-components.js
  // Class: ResultsComponent
  ExecuteQuery() {
    setTimeout(() => this.SetResults([]), 0); // !!!
    let tempResults = [];
    let lexIds = Object.keys(gSelectorComponent.lex);
    let totalSize = lexIds.map(lexId => gSelectorComponent.lex[lexId].entry.length).
                                          reduce((acc, val) => acc + val, 0);
    let resultsFilter = new ResultsFilter(totalSize);
    let processAtOnce = 20;

    this.activeSearches.forEach(timeoutId => clearTimeout(timeoutId));
    this.activeSearches.length = 0;

    /* Subfunction which emulates asynchronous processing in the (single-threaded) 
       JS environment by processing a slice of entries and then enqueuing the next 
       slice to be processed so that the browser has time to process user events 
       and render the GUI between slices. */
    let processingFunction = (lexIdIndex, entryIndex) => {
      if (lexIdIndex >= lexIds.length) {
        this.activeSearches.push(setTimeout(() => this.SetResults(tempResults), 0));
        return;
      }

      let entries = undefined;
      try {
        entries = gSelectorComponent.lex[lexIds[lexIdIndex]].entry;
      } catch (e) {
        // This happens when a lexicon is deselected while a search is running.
        // Abort search.
        return;
      }

      for (let i = 0; i < processAtOnce; ++i, ++entryIndex) {
        if (entryIndex >= entries.length) {
          this.activeSearches.push(setTimeout(processingFunction, 0, ++lexIdIndex, 0));
          return;
        }

        if (resultsFilter.TestEntry(entries[entryIndex])) {
          tempResults.push(entries[entryIndex]);
        }
      }

      this.activeSearches.push(setTimeout(processingFunction, 0, lexIdIndex, 
        entryIndex));
    };

    this.activeSearches.push(setTimeout(processingFunction, 0, 0, 0));
  }

Update: If I interpret Chrome's Performance tools correctly, this is a reflow issue caused by updating the progress bar. On every update, JS spends a lot of time in "Update Layer Tree" rendering operations, which seems to be the only difference which takes longer if more previous results are displayed. I wonder how I can get rid of this effect but still display progress.

Felix Dombek
  • 13,664
  • 17
  • 79
  • 131
  • Not sure if it's the good direction to go, but maybe the DOM is overloaded with the previous results. Are you using anything that provides "virtual DOM" like react or angular? That may be (part of) the solution. See for instance: http://reactkungfu.com/2015/10/the-difference-between-virtual-dom-and-dom/ – Joel Jul 31 '17 at 15:09

0 Answers0