Title: [110992] trunk/Source/WebCore
Revision
110992
Author
[email protected]
Date
2012-03-16 05:31:57 -0700 (Fri, 16 Mar 2012)

Log Message

[Performance] Optimize innerHTML and outerHTML
https://bugs.webkit.org/show_bug.cgi?id=81214

Reviewed by Adam Barth.

This patch makes innerHTML and outerHTML 2.4 times faster.

Performance test: https://bugs.webkit.org/attachment.cgi?id=132034
The performance test measures body.innerHTML for 3000 lines of HTML,
which is copied from the HTML spec.

- Chromium/Mac without the patch
div.innerHTML: 1658.6 ms
div.outerHTML: 4859.6 ms
body.innerHTML: 640.2 ms
body.outerHTML: 641.8 ms

- Chromium/Mac with the patch
div.innerHTML:  751.0 ms
div.outerHTML: 2096.0 ms
body.innerHTML: 271.2 ms
body.outerHTML: 271.2 ms

- Chromium/Linux without the patch
div.innerHTML:  950.4 ms
div.outerHTML: 2257.8 ms
body.innerHTML: 452.8 ms
body.outerHTML: 457.6 ms

- Chromium/Linux with the patch
div.innerHTML:  582.4 ms
div.outerHTML: 1283.0 ms
body.innerHTML: 233.0 ms
body.outerHTML: 233.4 ms

- AppleWebKit/Mac without the patch
div.innerHTML:  900.6 ms
div.outerHTML: 2245.2 ms
body.innerHTML: 462.6 ms
body.outerHTML: 468.0 ms

- AppleWebKit/Mac with the patch
div.innerHTML:  529.8  ms
div.outerHTML: 1090.2 ms
body.innerHTML: 239.2 ms
body.outerHTML: 239.2 ms

This patch applies the following two optimizations:

(a) Remove redundant copies between Vector<String> and StringBuilders
in MarkupAccumulator::serializeNodes(), MarkupAccumulator::appendStartTag(),
and MarkupAccumulator::appendEndTag().

    (Previous behavior)
    - Create a StringBuilder for each tag.
    - Append a created string in each StringBuilder to Vector<String>,
      parsing the DOM tree.
    - After the parsing, allocate a StringBuilder whose size is the sum
      of all Strings in Vector<String>.
    - Append all Strings in Vector<String> to the StringBuilder.
    (New behavior)
    - Allocate a StringBuilder with a default buffer size.
    - Append created strings to the StringBuilder, incrementally parsing
      the DOM tree.

(b) Optimize stringBuilder.append().
    (b-1) Replace stringBuilder.append("A") with stringBuilder.append('A').
          stringBuilder.append("A") requires to cast the characters to LChar*,
          and then call strlen("A"). stringBuilder.append('A') is faster.
    (b-2) Replace stringBuilder.append("AB") with stringBuilder.append('A')
          and stringBuilder.append('B'). In my experiment, appending characters
          one by one is faster than appending the characters at a breath if the
          number of characters is less than 3.
    (b-3) Hard-code a string length; i.e. replace stringBuilder.append("ABCDE")
          with stringBuilder.append("ABCDE", 5). While the former requires to call
          strlen("ABCDE"), the latter does not.

(a) improves performance by 170% ~ 200%. (b) improves performance by 30 ~ 40%.

Tests: fast/dom/Range/range-extract-contents.html
       fast/dom/serialize-nodes.xhtml
       fast/dom/XMLSerializer.html
       and all other tests that use innerHTML or outerHTML.
       No change in the test results.

* editing/MarkupAccumulator.cpp:
(WebCore::MarkupAccumulator::serializeNodes):
(WebCore::MarkupAccumulator::appendString):
(WebCore::MarkupAccumulator::appendStartTag):
(WebCore::MarkupAccumulator::appendEndTag):
(WebCore::MarkupAccumulator::concatenateMarkup):
(WebCore::MarkupAccumulator::appendQuotedURLAttributeValue):
(WebCore::MarkupAccumulator::appendComment):
(WebCore::MarkupAccumulator::appendDocumentType):
(WebCore::MarkupAccumulator::appendProcessingInstruction):
(WebCore::MarkupAccumulator::appendOpenTag):
(WebCore::MarkupAccumulator::appendAttribute):
(WebCore::MarkupAccumulator::appendCDATASection):
* editing/MarkupAccumulator.h:
(MarkupAccumulator):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (110991 => 110992)


--- trunk/Source/WebCore/ChangeLog	2012-03-16 12:23:07 UTC (rev 110991)
+++ trunk/Source/WebCore/ChangeLog	2012-03-16 12:31:57 UTC (rev 110992)
@@ -1,3 +1,106 @@
+2012-03-16  Kentaro Hara  <[email protected]>
+
+        [Performance] Optimize innerHTML and outerHTML
+        https://bugs.webkit.org/show_bug.cgi?id=81214
+
+        Reviewed by Adam Barth.
+
+        This patch makes innerHTML and outerHTML 2.4 times faster.
+
+        Performance test: https://bugs.webkit.org/attachment.cgi?id=132034
+        The performance test measures body.innerHTML for 3000 lines of HTML,
+        which is copied from the HTML spec.
+
+        - Chromium/Mac without the patch
+        div.innerHTML: 1658.6 ms
+        div.outerHTML: 4859.6 ms
+        body.innerHTML: 640.2 ms
+        body.outerHTML: 641.8 ms
+
+        - Chromium/Mac with the patch
+        div.innerHTML:  751.0 ms
+        div.outerHTML: 2096.0 ms
+        body.innerHTML: 271.2 ms
+        body.outerHTML: 271.2 ms
+
+        - Chromium/Linux without the patch
+        div.innerHTML:  950.4 ms
+        div.outerHTML: 2257.8 ms
+        body.innerHTML: 452.8 ms
+        body.outerHTML: 457.6 ms
+
+        - Chromium/Linux with the patch
+        div.innerHTML:  582.4 ms
+        div.outerHTML: 1283.0 ms
+        body.innerHTML: 233.0 ms
+        body.outerHTML: 233.4 ms
+
+        - AppleWebKit/Mac without the patch
+        div.innerHTML:  900.6 ms
+        div.outerHTML: 2245.2 ms
+        body.innerHTML: 462.6 ms
+        body.outerHTML: 468.0 ms
+
+        - AppleWebKit/Mac with the patch
+        div.innerHTML:  529.8  ms
+        div.outerHTML: 1090.2 ms
+        body.innerHTML: 239.2 ms
+        body.outerHTML: 239.2 ms
+
+        This patch applies the following two optimizations:
+
+        (a) Remove redundant copies between Vector<String> and StringBuilders
+        in MarkupAccumulator::serializeNodes(), MarkupAccumulator::appendStartTag(),
+        and MarkupAccumulator::appendEndTag().
+
+            (Previous behavior)
+            - Create a StringBuilder for each tag.
+            - Append a created string in each StringBuilder to Vector<String>,
+              parsing the DOM tree.
+            - After the parsing, allocate a StringBuilder whose size is the sum
+              of all Strings in Vector<String>.
+            - Append all Strings in Vector<String> to the StringBuilder.
+            (New behavior)
+            - Allocate a StringBuilder with a default buffer size.
+            - Append created strings to the StringBuilder, incrementally parsing
+              the DOM tree.
+
+        (b) Optimize stringBuilder.append().
+            (b-1) Replace stringBuilder.append("A") with stringBuilder.append('A').
+                  stringBuilder.append("A") requires to cast the characters to LChar*,
+                  and then call strlen("A"). stringBuilder.append('A') is faster.
+            (b-2) Replace stringBuilder.append("AB") with stringBuilder.append('A')
+                  and stringBuilder.append('B'). In my experiment, appending characters
+                  one by one is faster than appending the characters at a breath if the
+                  number of characters is less than 3.
+            (b-3) Hard-code a string length; i.e. replace stringBuilder.append("ABCDE")
+                  with stringBuilder.append("ABCDE", 5). While the former requires to call
+                  strlen("ABCDE"), the latter does not.
+
+        (a) improves performance by 170% ~ 200%. (b) improves performance by 30 ~ 40%.
+
+        Tests: fast/dom/Range/range-extract-contents.html
+               fast/dom/serialize-nodes.xhtml
+               fast/dom/XMLSerializer.html
+               and all other tests that use innerHTML or outerHTML.
+               No change in the test results.
+
+        * editing/MarkupAccumulator.cpp:
+        (WebCore::MarkupAccumulator::serializeNodes):
+        (WebCore::MarkupAccumulator::appendString):
+        (WebCore::MarkupAccumulator::appendStartTag):
+        (WebCore::MarkupAccumulator::appendEndTag):
+        (WebCore::MarkupAccumulator::concatenateMarkup):
+        (WebCore::MarkupAccumulator::appendQuotedURLAttributeValue):
+        (WebCore::MarkupAccumulator::appendComment):
+        (WebCore::MarkupAccumulator::appendDocumentType):
+        (WebCore::MarkupAccumulator::appendProcessingInstruction):
+        (WebCore::MarkupAccumulator::appendOpenTag):
+        (WebCore::MarkupAccumulator::appendAttribute):
+        (WebCore::MarkupAccumulator::appendCDATASection):
+        * editing/MarkupAccumulator.h:
+        (MarkupAccumulator):
+
 2012-03-16  Kihong Kwon  <[email protected]>
 
         Support for Battery Status API

Modified: trunk/Source/WebCore/editing/MarkupAccumulator.cpp (110991 => 110992)


--- trunk/Source/WebCore/editing/MarkupAccumulator.cpp	2012-03-16 12:23:07 UTC (rev 110991)
+++ trunk/Source/WebCore/editing/MarkupAccumulator.cpp	2012-03-16 12:31:57 UTC (rev 110992)
@@ -37,7 +37,6 @@
 #include "KURL.h"
 #include "ProcessingInstruction.h"
 #include "XMLNSNames.h"
-#include <wtf/text/StringBuilder.h>
 #include <wtf/unicode/CharacterNames.h>
 
 namespace WebCore {
@@ -87,11 +86,8 @@
 
 String MarkupAccumulator::serializeNodes(Node* targetNode, Node* nodeToSkip, EChildrenOnly childrenOnly)
 {
-    StringBuilder result;
     serializeNodesWithNamespaces(targetNode, nodeToSkip, childrenOnly, 0);
-    result.reserveCapacity(length());
-    concatenateMarkup(result);
-    return result.toString();
+    return m_markup.toString();
 }
 
 void MarkupAccumulator::serializeNodesWithNamespaces(Node* targetNode, Node* nodeToSkip, EChildrenOnly childrenOnly, const Namespaces* namespaces)
@@ -134,23 +130,19 @@
 
 void MarkupAccumulator::appendString(const String& string)
 {
-    m_succeedingMarkup.append(string);
+    m_markup.append(string);
 }
 
 void MarkupAccumulator::appendStartTag(Node* node, Namespaces* namespaces)
 {
-    StringBuilder markup;
-    appendStartMarkup(markup, node, namespaces);
-    appendString(markup.toString());
+    appendStartMarkup(m_markup, node, namespaces);
     if (m_nodes)
         m_nodes->append(node);
 }
 
 void MarkupAccumulator::appendEndTag(Node* node)
 {
-    StringBuilder markup;
-    appendEndMarkup(markup, node);
-    appendString(markup.toString());
+    appendEndMarkup(m_markup, node);
 }
 
 size_t MarkupAccumulator::totalLength(const Vector<String>& strings)
@@ -161,13 +153,9 @@
     return length;
 }
 
-// FIXME: This is a very inefficient way of accumulating the markup.
-// We're converting results of appendStartMarkup and appendEndMarkup from StringBuilder to String
-// and then back to StringBuilder and again to String here.
 void MarkupAccumulator::concatenateMarkup(StringBuilder& result)
 {
-    for (size_t i = 0; i < m_succeedingMarkup.size(); ++i)
-        result.append(m_succeedingMarkup[i]);
+    result.append(m_markup);
 }
 
 void MarkupAccumulator::appendAttributeValue(StringBuilder& result, const String& attribute, bool documentIsHTML)
@@ -184,13 +172,13 @@
 {
     ASSERT(element->isURLAttribute(const_cast<Attribute*>(&attribute)));
     const String resolvedURLString = resolveURLIfNeeded(element, attribute.value());
-    UChar quoteChar = '\"';
+    UChar quoteChar = '"';
     String strippedURLString = resolvedURLString.stripWhiteSpace();
     if (protocolIsJavaScript(strippedURLString)) {
         // minimal escaping for _javascript_ urls
         if (strippedURLString.contains('"')) {
             if (strippedURLString.contains('\''))
-                strippedURLString.replace('\"', "&quot;");
+                strippedURLString.replace('"', "&quot;");
             else
                 quoteChar = '\'';
         }
@@ -301,9 +289,11 @@
 void MarkupAccumulator::appendComment(StringBuilder& result, const String& comment)
 {
     // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
-    result.append("<!--");
+    static const char commentBegin[] = "<!--";
+    result.append(commentBegin, sizeof(commentBegin) - 1);
     result.append(comment);
-    result.append("-->");
+    static const char commentEnd[] = "-->";
+    result.append(commentEnd, sizeof(commentEnd) - 1);
 }
 
 void MarkupAccumulator::appendDocumentType(StringBuilder& result, const DocumentType* n)
@@ -311,38 +301,45 @@
     if (n->name().isEmpty())
         return;
 
-    result.append("<!DOCTYPE ");
+    static const char doctypeString[] = "<!DOCTYPE ";
+    result.append(doctypeString, sizeof(doctypeString) - 1);
     result.append(n->name());
     if (!n->publicId().isEmpty()) {
-        result.append(" PUBLIC \"");
+        static const char publicString[] = " PUBLIC \"";
+        result.append(publicString, sizeof(publicString) - 1);
         result.append(n->publicId());
-        result.append("\"");
+        result.append('"');
         if (!n->systemId().isEmpty()) {
-            result.append(" \"");
+            result.append(' ');
+            result.append('"');
             result.append(n->systemId());
-            result.append("\"");
+            result.append('"');
         }
     } else if (!n->systemId().isEmpty()) {
-        result.append(" SYSTEM \"");
+        static const char systemString[] = " SYSTEM \"";
+        result.append(systemString, sizeof(systemString) - 1);
         result.append(n->systemId());
-        result.append("\"");
+        result.append('"');
     }
     if (!n->internalSubset().isEmpty()) {
-        result.append(" [");
+        result.append(' ');
+        result.append('[');
         result.append(n->internalSubset());
-        result.append("]");
+        result.append(']');
     }
-    result.append(">");
+    result.append('>');
 }
 
 void MarkupAccumulator::appendProcessingInstruction(StringBuilder& result, const String& target, const String& data)
 {
     // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
-    result.append("<?");
+    result.append('<');
+    result.append('?');
     result.append(target);
-    result.append(" ");
+    result.append(' ');
     result.append(data);
-    result.append("?>");
+    result.append('?');
+    result.append('>');
 }
 
 void MarkupAccumulator::appendElement(StringBuilder& result, Element* element, Namespaces* namespaces)
@@ -366,7 +363,7 @@
     result.append('<');
     result.append(element->nodeNamePreservingCase());
     if (!element->document()->isHTMLDocument() && namespaces && shouldAddNamespaceElement(element))
-        appendNamespace(result, element->prefix(), element->namespaceURI(), *namespaces);    
+        appendNamespace(result, element->prefix(), element->namespaceURI(), *namespaces);
 }
 
 void MarkupAccumulator::appendCloseTag(StringBuilder& result, Element* element)
@@ -395,9 +392,9 @@
     if (element->isURLAttribute(const_cast<Attribute*>(&attribute)))
         appendQuotedURLAttributeValue(result, element, attribute);
     else {
-        result.append('\"');
+        result.append('"');
         appendAttributeValue(result, attribute.value(), documentIsHTML);
-        result.append('\"');
+        result.append('"');
     }
 
     if (!documentIsHTML && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces))
@@ -407,9 +404,11 @@
 void MarkupAccumulator::appendCDATASection(StringBuilder& result, const String& section)
 {
     // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
-    result.append("<![CDATA[");
+    static const char cdataBegin[] = "<![CDATA[";
+    result.append(cdataBegin, sizeof(cdataBegin) - 1);
     result.append(section);
-    result.append("]]>");
+    static const char cdataEnd[] = "]]>";
+    result.append(cdataEnd, sizeof(cdataEnd) - 1);
 }
 
 void MarkupAccumulator::appendStartMarkup(StringBuilder& result, const Node* node, Namespaces* namespaces)

Modified: trunk/Source/WebCore/editing/MarkupAccumulator.h (110991 => 110992)


--- trunk/Source/WebCore/editing/MarkupAccumulator.h	2012-03-16 12:23:07 UTC (rev 110991)
+++ trunk/Source/WebCore/editing/MarkupAccumulator.h	2012-03-16 12:31:57 UTC (rev 110992)
@@ -30,6 +30,7 @@
 #include "markup.h"
 #include <wtf/HashMap.h>
 #include <wtf/Vector.h>
+#include <wtf/text/StringBuilder.h>
 
 namespace WebCore {
 
@@ -78,7 +79,7 @@
     void appendStartTag(Node*, Namespaces* = 0);
     virtual void appendEndTag(Node*);
     static size_t totalLength(const Vector<String>&);
-    size_t length() const { return totalLength(m_succeedingMarkup); }
+    size_t length() const { return m_markup.length(); }
     void concatenateMarkup(StringBuilder&);
     void appendAttributeValue(StringBuilder&, const String&, bool);
     virtual void appendCustomAttributes(StringBuilder&, Element*, Namespaces*);
@@ -108,7 +109,7 @@
     void appendQuotedURLAttributeValue(StringBuilder&, const Element*, const Attribute&);
     void serializeNodesWithNamespaces(Node* targetNode, Node* nodeToSkip, EChildrenOnly, const Namespaces*);
 
-    Vector<String> m_succeedingMarkup;
+    StringBuilder m_markup;
     const EAbsoluteURLs m_resolveURLsMethod;
 };
 
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to