Bug 87627 - [Performance] Optimize querySelector() and querySelectorAll() by removing redundant dummy style sheet creations
Summary: [Performance] Optimize querySelector() and querySelectorAll() by removing red...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: CSS (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Kentaro Hara
URL:
Keywords:
Depends on:
Blocks: 87625
  Show dependency treegraph
 
Reported: 2012-05-28 00:12 PDT by Kentaro Hara
Modified: 2012-05-29 09:22 PDT (History)
6 users (show)

See Also:


Attachments
Performance tests (4.98 KB, text/html)
2012-05-28 00:13 PDT, Kentaro Hara
no flags Details
Patch (14.02 KB, patch)
2012-05-28 00:15 PDT, Kentaro Hara
no flags Details | Formatted Diff | Diff
Patch (12.87 KB, patch)
2012-05-28 04:05 PDT, Kentaro Hara
no flags Details | Formatted Diff | Diff
Patch (8.37 KB, patch)
2012-05-28 21:13 PDT, Kentaro Hara
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Kentaro Hara 2012-05-28 00:12:56 PDT
Currently Node::querySelector() and Node::querySelectorAll() have the following call path:

    PassRefPtr<Element> Node::querySelector(const String& selectors, ExceptionCode& ec)
    {
        CSSParser parser(document()); /* bottleneck (1) */
        CSSSelectorList querySelectorList;
        parser.parseSelector(selectors, querySelectorList);
        ...;
    }

    void CSSParser::parseSelector(const String& string, CSSSelectorList& selectorList)
    {
        RefPtr<StyleSheetInternal> dummyStyleSheet = StyleSheetInternal::create(); /* bottleneck (2) */
        setStyleSheet(dummyStyleSheet.get());
        ...;
    }

I've found that 'CSSParser parser(document())' and 'StyleSheetInternal::create()' are bottlenecks of querySelector() and querySelectorAll().

We do not need to construct a CSSParser and a dummyStyleSheet every time. By caching the CSSParser with a dummyStyleSheet on Document, we can remove the CSSParser constructor and the dummyStyleSheet creation from the call path of querySelector() and querySelectorAll().
Comment 1 Kentaro Hara 2012-05-28 00:13:35 PDT
Created attachment 144288 [details]
Performance tests
Comment 2 Kentaro Hara 2012-05-28 00:15:19 PDT
Created attachment 144289 [details]
Patch
Comment 3 Antti Koivisto 2012-05-28 02:12:30 PDT
Comment on attachment 144289 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=144289&action=review

> Source/WebCore/css/CSSParser.cpp:1127
> +void CSSParser::parseSelectorWithDummyStyleSheet(const String& string, CSSSelectorList& selectorList)
> +{
> +    setDummyStyleSheet();
> +    parseSelector(string, selectorList);
>  }

Why does this case still need dummy stylesheet? Could you figure out how to get rid of it completely at least for selector parsing? I think it might be just matter of adding some null checks to code that deals with the stylesheet during the parsing.
Comment 4 Kentaro Hara 2012-05-28 04:05:52 PDT
Created attachment 144324 [details]
Patch
Comment 5 Kentaro Hara 2012-05-28 04:06:48 PDT
(In reply to comment #3)
> (From update of attachment 144289 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=144289&action=review
> 
> > Source/WebCore/css/CSSParser.cpp:1127
> > +void CSSParser::parseSelectorWithDummyStyleSheet(const String& string, CSSSelectorList& selectorList)
> > +{
> > +    setDummyStyleSheet();
> > +    parseSelector(string, selectorList);
> >  }
> 
> Why does this case still need dummy stylesheet? Could you figure out how to get rid of it completely at least for selector parsing? I think it might be just matter of adding some null checks to code that deals with the stylesheet during the parsing.

Makes sense. Removed a dummy style sheet.
Comment 6 Antti Koivisto 2012-05-28 06:26:22 PDT
Comment on attachment 144324 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=144324&action=review

> Source/WebCore/ChangeLog:83
> +        - We do not need to construct a CSSParser every time. This patch caches
> +        the CSSParser on Document, by which we can remove the CSSParser constructor
> +        from querySelector() and querySelectorAll().

This may be problematic as we grab bunch of settings and state from the document on CSSParser construction. If those change the cached parser instance will need to be invalidated. The cached instance could also be used more widely than just querySelector.

I think this part should be done in a separate patch. Lets focus on avoiding dummy stylesheet construction here.
Comment 7 Kentaro Hara 2012-05-28 21:13:53 PDT
Created attachment 144437 [details]
Patch
Comment 8 Kentaro Hara 2012-05-28 21:15:44 PDT
(In reply to comment #6)
> This may be problematic as we grab bunch of settings and state from the document on CSSParser construction. If those change the cached parser instance will need to be invalidated. The cached instance could also be used more widely than just querySelector.
> 
> I think this part should be done in a separate patch. Lets focus on avoiding dummy stylesheet construction here.

Makes sense. I'll do it in a separate patch with more investigation (Caching a CSSParser on Document is important since it improves performance by 30%~).

I uploaded a patch that just removes a dummy style sheet.
Comment 9 Antti Koivisto 2012-05-29 00:42:37 PDT
Comment on attachment 144437 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=144437&action=review

r=me

> Source/WebCore/ChangeLog:12
> +        Performance tests: https://bugs.webkit.org/attachment.cgi?id=144288

You should add a version of the performance test to PerformanceTests/
Comment 10 WebKit Review Bot 2012-05-29 01:43:58 PDT
Comment on attachment 144437 [details]
Patch

Clearing flags on attachment: 144437

Committed r118736: <http://trac.webkit.org/changeset/118736>
Comment 11 WebKit Review Bot 2012-05-29 01:44:03 PDT
All reviewed patches have been landed.  Closing bug.
Comment 12 Darin Adler 2012-05-29 09:22:19 PDT
Comment on attachment 144437 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=144437&action=review

> Source/WebCore/css/CSSGrammar.y:963
>          if (p->m_styleSheet)
> -            $$->setTag(QualifiedName(namespacePrefix, $2,
> -                                      p->m_styleSheet->determineNamespace(namespacePrefix)));
> -        else // FIXME: Shouldn't this case be an error?
> -            $$->setTag(QualifiedName(nullAtom, $2, p->m_defaultNamespace));
> +            $$->setTag(QualifiedName(namespacePrefix, $2, p->m_styleSheet->determineNamespace(namespacePrefix)));
> +        else
> +            $$->setTag(QualifiedName(namespacePrefix, $2, p->m_defaultNamespace));

A better way to factor this to avoid repeating an if statement three times is to make a CSSParser member function that does this same job. Something like this:

    $$->setTag(p->namespacedName(namespacePrefix, $4));

Not sure about the function name namespacedName, but with the right function name this would be a better factoring between CSSGrammar.y and CSSParser.h/cpp, which exists to provide helper functions like that.