Title: [133214] trunk/Source/WebCore
Revision
133214
Author
an...@apple.com
Date
2012-11-01 12:41:08 -0700 (Thu, 01 Nov 2012)

Log Message

REGRESSION (r132941): attribute modification 10% performance regression
https://bugs.webkit.org/show_bug.cgi?id=100873

Reviewed by Ojan Vafai.

Don't do the class change finding by mutating SpaceSplitString. It is slow. Instead use a bit vector
to mark the unchanged classes

* css/StyleResolver.cpp:
(WebCore):
* css/StyleResolver.h:
(WebCore::StyleResolver::hasSelectorForAttribute):
(WebCore):
(WebCore::StyleResolver::hasSelectorForClass):
(WebCore::StyleResolver::hasSelectorForId):
        
    Inlined these and moved value validity testing to clients.

* dom/Element.cpp:
(WebCore::checkNeedsStyleInvalidationForIdChange):
(WebCore):
(WebCore::Element::attributeChanged):
    
    Clean up id testing too.

(WebCore::checkNeedsStyleInvalidationForClassChange):
        
    Use bit vector for marking seen values. Avoids allocations and reffing.

(WebCore::Element::classAttributeChanged):
        
    Don't test if style is already invalid.

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (133213 => 133214)


--- trunk/Source/WebCore/ChangeLog	2012-11-01 19:31:48 UTC (rev 133213)
+++ trunk/Source/WebCore/ChangeLog	2012-11-01 19:41:08 UTC (rev 133214)
@@ -1,3 +1,38 @@
+2012-11-01  Antti Koivisto  <an...@apple.com>
+
+        REGRESSION (r132941): attribute modification 10% performance regression
+        https://bugs.webkit.org/show_bug.cgi?id=100873
+
+        Reviewed by Ojan Vafai.
+
+        Don't do the class change finding by mutating SpaceSplitString. It is slow. Instead use a bit vector
+        to mark the unchanged classes
+
+        * css/StyleResolver.cpp:
+        (WebCore):
+        * css/StyleResolver.h:
+        (WebCore::StyleResolver::hasSelectorForAttribute):
+        (WebCore):
+        (WebCore::StyleResolver::hasSelectorForClass):
+        (WebCore::StyleResolver::hasSelectorForId):
+        
+            Inlined these and moved value validity testing to clients.
+
+        * dom/Element.cpp:
+        (WebCore::checkNeedsStyleInvalidationForIdChange):
+        (WebCore):
+        (WebCore::Element::attributeChanged):
+    
+            Clean up id testing too.
+
+        (WebCore::checkNeedsStyleInvalidationForClassChange):
+        
+            Use bit vector for marking seen values. Avoids allocations and reffing.
+
+        (WebCore::Element::classAttributeChanged):
+        
+            Don't test if style is already invalid.
+
 2012-11-01  Ryosuke Niwa  <rn...@webkit.org>
 
         [Mac] Crash in Range::editingStartPosition

Modified: trunk/Source/WebCore/css/StyleResolver.cpp (133213 => 133214)


--- trunk/Source/WebCore/css/StyleResolver.cpp	2012-11-01 19:31:48 UTC (rev 133213)
+++ trunk/Source/WebCore/css/StyleResolver.cpp	2012-11-01 19:41:08 UTC (rev 133214)
@@ -4249,25 +4249,6 @@
     }
 }
 
-bool StyleResolver::hasSelectorForAttribute(const AtomicString &attrname) const
-{
-    return m_features.attrsInRules.contains(attrname.impl());
-}
-
-bool StyleResolver::hasSelectorForClass(const AtomicString& classValue) const
-{
-    if (classValue.isEmpty())
-        return false;
-    return m_features.classesInRules.contains(classValue.impl());
-}
-
-bool StyleResolver::hasSelectorForId(const AtomicString& idValue) const
-{
-    if (idValue.isEmpty())
-        return false;
-    return m_features.idsInRules.contains(idValue.impl());
-}
-
 void StyleResolver::addViewportDependentMediaQueryResult(const MediaQueryExp* expr, bool result)
 {
     m_viewportDependentMediaQueryResults.append(adoptPtr(new MediaQueryResult(*expr, result)));

Modified: trunk/Source/WebCore/css/StyleResolver.h (133213 => 133214)


--- trunk/Source/WebCore/css/StyleResolver.h	2012-11-01 19:31:48 UTC (rev 133213)
+++ trunk/Source/WebCore/css/StyleResolver.h	2012-11-01 19:41:08 UTC (rev 133214)
@@ -518,6 +518,24 @@
     friend bool operator!=(const MatchRanges&, const MatchRanges&);
 };
 
+inline bool StyleResolver::hasSelectorForAttribute(const AtomicString &attributeName) const
+{
+    ASSERT(!attributeName.isEmpty());
+    return m_features.attrsInRules.contains(attributeName.impl());
+}
+
+inline bool StyleResolver::hasSelectorForClass(const AtomicString& classValue) const
+{
+    ASSERT(!classValue.isEmpty());
+    return m_features.classesInRules.contains(classValue.impl());
+}
+
+inline bool StyleResolver::hasSelectorForId(const AtomicString& idValue) const
+{
+    ASSERT(!idValue.isEmpty());
+    return m_features.idsInRules.contains(idValue.impl());
+}
+
 } // namespace WebCore
 
 #endif // StyleResolver_h

Modified: trunk/Source/WebCore/dom/Element.cpp (133213 => 133214)


--- trunk/Source/WebCore/dom/Element.cpp	2012-11-01 19:31:48 UTC (rev 133213)
+++ trunk/Source/WebCore/dom/Element.cpp	2012-11-01 19:41:08 UTC (rev 133214)
@@ -77,6 +77,7 @@
 #include "XMLNSNames.h"
 #include "XMLNames.h"
 #include "htmlediting.h"
+#include <wtf/BitVector.h>
 #include <wtf/text/CString.h>
 
 #if ENABLE(SVG)
@@ -702,32 +703,43 @@
     return value;
 }
 
+static bool checkNeedsStyleInvalidationForIdChange(const AtomicString& oldId, const AtomicString& newId, StyleResolver* styleResolver)
+{
+    ASSERT(newId != oldId);
+    if (!oldId.isEmpty() && styleResolver->hasSelectorForId(oldId))
+        return true;
+    if (!newId.isEmpty() && styleResolver->hasSelectorForId(newId))
+        return true;
+    return false;
+}
+
 void Element::attributeChanged(const QualifiedName& name, const AtomicString& newValue)
 {
     parseAttribute(Attribute(name, newValue));
 
     document()->incDOMTreeVersion();
 
+    StyleResolver* styleResolver = document()->styleResolverIfExists();
+    bool testShouldInvalidateStyle = attached() && styleResolver && styleChangeType() < FullStyleChange;
+    bool shouldInvalidateStyle = false;
+
     if (isIdAttributeName(name)) {
         AtomicString oldId = attributeData()->idForStyleResolution();
         AtomicString newId = makeIdForStyleResolution(newValue, document()->inQuirksMode());
         if (newId != oldId) {
             attributeData()->setIdForStyleResolution(newId);
-            StyleResolver* styleResolver = document()->styleResolverIfExists();
-            if (attached() && (!styleResolver || (styleResolver->hasSelectorForId(newId) || styleResolver->hasSelectorForId(oldId))))
-                setNeedsStyleRecalc();
+            shouldInvalidateStyle = testShouldInvalidateStyle && checkNeedsStyleInvalidationForIdChange(oldId, newId, styleResolver);
         }
     } else if (name == HTMLNames::nameAttr)
         setHasName(!newValue.isNull());
 
-    if (!needsStyleRecalc() && document()->attached()) {
-        StyleResolver* styleResolver = document()->styleResolverIfExists();
-        if (!styleResolver || styleResolver->hasSelectorForAttribute(name.localName()))
-            setNeedsStyleRecalc();
-    }
+    shouldInvalidateStyle |= testShouldInvalidateStyle && styleResolver->hasSelectorForAttribute(name.localName());
 
     invalidateNodeListCachesInAncestors(&name, this);
 
+    if (shouldInvalidateStyle)
+        setNeedsStyleRecalc();
+
     if (AXObjectCache::accessibilityEnabled())
         document()->axObjectCache()->handleAttributeChanged(name, this);
 }
@@ -765,49 +777,72 @@
     return classStringHasClassName(newClassString.characters16(), length);
 }
 
-static void collectAddedAndRemovedClasses(Vector<AtomicString, 4>& addedAndRemovedClasses, SpaceSplitString& oldClasses, const SpaceSplitString& newClasses)
+static bool checkNeedsStyleInvalidationForClassChange(const SpaceSplitString& changedClasses, StyleResolver* styleResolver)
 {
-    for (unsigned i = 0; i < newClasses.size(); ++i) {
-        if (oldClasses.remove(newClasses[i]))
+    unsigned changedSize = changedClasses.size();
+    for (unsigned i = 0; i < changedSize; ++i) {
+        if (styleResolver->hasSelectorForClass(changedClasses[i]))
+            return true;
+    }
+    return false;
+}
+
+static bool checkNeedsStyleInvalidationForClassChange(const SpaceSplitString& oldClasses, const SpaceSplitString& newClasses, StyleResolver* styleResolver)
+{
+    unsigned oldSize = oldClasses.size();
+    if (!oldSize)
+        return checkNeedsStyleInvalidationForClassChange(newClasses, styleResolver);
+    BitVector remainingClassBits;
+    remainingClassBits.ensureSize(oldSize);
+    // Class vectors tend to be very short. This is faster than using a hash table.
+    unsigned newSize = newClasses.size();
+    for (unsigned i = 0; i < newSize; ++i) {
+        for (unsigned j = 0; j < oldSize; ++j) {
+            if (newClasses[i] == oldClasses[j]) {
+                remainingClassBits.quickSet(j);
+                continue;
+            }
+        }
+        if (styleResolver->hasSelectorForClass(newClasses[i]))
+            return true;
+    }
+    for (unsigned i = 0; i < oldSize; ++i) {
+        // If the bit is not set the the corresponding class has been removed.
+        if (remainingClassBits.quickGet(i))
             continue;
-        addedAndRemovedClasses.append(newClasses[i]);
+        if (styleResolver->hasSelectorForClass(oldClasses[i]))
+            return true;
     }
-    for (unsigned i = 0; i < oldClasses.size(); ++i)
-        addedAndRemovedClasses.append(oldClasses[i]);
+    return false;
 }
 
 void Element::classAttributeChanged(const AtomicString& newClassString)
 {
-    bool shouldInvalidateStyle = attached() && document()->styleResolverIfExists();
-    Vector<AtomicString, 4> addedAndRemovedClasses;
+    StyleResolver* styleResolver = document()->styleResolverIfExists();
+    bool testShouldInvalidateStyle = attached() && styleResolver && styleChangeType() < FullStyleChange;
+    bool shouldInvalidateStyle = false;
 
     if (classStringHasClassName(newClassString)) {
         const ElementAttributeData* attributeData = ensureAttributeData();
         const bool shouldFoldCase = document()->inQuirksMode();
-        SpaceSplitString oldClasses = attributeData->classNames();
+        const SpaceSplitString oldClasses = attributeData->classNames();
+
         attributeData->setClass(newClassString, shouldFoldCase);
-        if (shouldInvalidateStyle)
-            collectAddedAndRemovedClasses(addedAndRemovedClasses, oldClasses, attributeData->classNames());
+
+        const SpaceSplitString& newClasses = attributeData->classNames();
+        shouldInvalidateStyle = testShouldInvalidateStyle && checkNeedsStyleInvalidationForClassChange(oldClasses, newClasses, styleResolver);
     } else if (const ElementAttributeData* attributeData = this->attributeData()) {
-        if (shouldInvalidateStyle) {
-            const SpaceSplitString& oldClasses = attributeData->classNames();
-            for (unsigned i = 0; i < oldClasses.size(); ++i)
-                addedAndRemovedClasses.append(oldClasses[i]);
-        }
+        const SpaceSplitString& oldClasses = attributeData->classNames();
+        shouldInvalidateStyle = testShouldInvalidateStyle && checkNeedsStyleInvalidationForClassChange(oldClasses, styleResolver);
+
         attributeData->clearClass();
     }
 
     if (DOMTokenList* classList = optionalClassList())
         static_cast<ClassList*>(classList)->reset(newClassString);
 
-    if (!shouldInvalidateStyle)
-        return;
-    for (unsigned i = 0; i < addedAndRemovedClasses.size(); ++i) {
-        if (document()->styleResolverIfExists()->hasSelectorForClass(addedAndRemovedClasses[i])) {
-            setNeedsStyleRecalc();
-            return;
-        }
-    }
+    if (shouldInvalidateStyle)
+        setNeedsStyleRecalc();
 }
 
 // Returns true is the given attribute is an event handler.
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to