Title: [276840] trunk/Source/WTF
Revision
276840
Author
[email protected]
Date
2021-04-30 09:02:52 -0700 (Fri, 30 Apr 2021)

Log Message

Optimize SortedArrayMap by using linear search for small arrays
https://bugs.webkit.org/show_bug.cgi?id=225223

Reviewed by Yusuke Suzuki.

* wtf/SortedArrayMap.h: Added a base class SortedArrayBase that contains a constant
for the cutoff between linear and binary search. Based on a quick empirical test,
it seems faster to do linear search for arrays that have less then 20 elements.
While doing this also switched from tryBinarySearch to std::find/find_if/lower_bound
because the standard library function templates should be excellent for this purpose,
and there's no reason to use our own. Also filled out the operations for the
ComparableASCIILiteral family so that the "<" operator works in either direction,
since that's needed for compatibility with standard library algorithms.

Modified Paths

Diff

Modified: trunk/Source/WTF/ChangeLog (276839 => 276840)


--- trunk/Source/WTF/ChangeLog	2021-04-30 15:19:26 UTC (rev 276839)
+++ trunk/Source/WTF/ChangeLog	2021-04-30 16:02:52 UTC (rev 276840)
@@ -1,3 +1,19 @@
+2021-04-30  Darin Adler  <[email protected]>
+
+        Optimize SortedArrayMap by using linear search for small arrays
+        https://bugs.webkit.org/show_bug.cgi?id=225223
+
+        Reviewed by Yusuke Suzuki.
+
+        * wtf/SortedArrayMap.h: Added a base class SortedArrayBase that contains a constant
+        for the cutoff between linear and binary search. Based on a quick empirical test,
+        it seems faster to do linear search for arrays that have less then 20 elements.
+        While doing this also switched from tryBinarySearch to std::find/find_if/lower_bound
+        because the standard library function templates should be excellent for this purpose,
+        and there's no reason to use our own. Also filled out the operations for the
+        ComparableASCIILiteral family so that the "<" operator works in either direction,
+        since that's needed for compatibility with standard library algorithms.
+
 2021-04-29  Ben Nham  <[email protected]>
 
         Reduce memory footprint for background tabs

Modified: trunk/Source/WTF/wtf/SortedArrayMap.h (276839 => 276840)


--- trunk/Source/WTF/wtf/SortedArrayMap.h	2021-04-30 15:19:26 UTC (rev 276839)
+++ trunk/Source/WTF/wtf/SortedArrayMap.h	2021-04-30 16:02:52 UTC (rev 276840)
@@ -35,7 +35,17 @@
 // The array passed to the constructor has std::pair elements: keys first and values second.
 // The array and the SortedArrayMap should typically both be global constant expressions.
 
-template<typename ArrayType> class SortedArrayMap {
+class SortedArrayBase {
+protected:
+    // Some informal empirical tests indicate that arrays shorter than this are faster to
+    // search with linear search than with binary search. Even if we don't get this threshold
+    // exactly right, it's helpful for both performance and code size to use linear search at
+    // least for very small arrays, and important for performance to make sure that we use
+    // binary search for much larger ones.
+    static constexpr size_t binarySearchThreshold = 20;
+};
+
+template<typename ArrayType> class SortedArrayMap : public SortedArrayBase {
 public:
     using ElementType = typename std::remove_extent_t<ArrayType>;
     using ValueType = typename ElementType::second_type;
@@ -52,7 +62,7 @@
     const ArrayType& m_array;
 };
 
-template<typename ArrayType> class SortedArraySet {
+template<typename ArrayType> class SortedArraySet : public SortedArrayBase {
 public:
     constexpr SortedArraySet(const ArrayType&);
     template<typename KeyArgument> bool contains(const KeyArgument&) const;
@@ -88,6 +98,9 @@
 bool operator<(ComparableStringView, ComparableASCIILiteral);
 bool operator<(ComparableStringView, ComparableCaseFoldingASCIILiteral);
 bool operator<(ComparableStringView, ComparableLettersLiteral);
+bool operator<(ComparableASCIILiteral, ComparableStringView);
+bool operator<(ComparableCaseFoldingASCIILiteral, ComparableStringView);
+bool operator<(ComparableLettersLiteral, ComparableStringView);
 
 template<typename OtherType> bool operator==(OtherType, ComparableStringView);
 template<typename OtherType> bool operator!=(ComparableStringView, OtherType);
@@ -128,12 +141,21 @@
     auto parsedKey = ElementType::first_type::parse(key);
     if (!parsedKey)
         return nullptr;
-    // FIXME: We should enhance tryBinarySearch so it can deduce ElementType.
-    // FIXME: If the array's size is small enough, should do linear search since it is more efficient than binary search.
-    auto element = tryBinarySearch<ElementType>(m_array, std::size(m_array), *parsedKey, [] (auto* element) {
-        return element->first;
-    });
-    return element ? &element->second : nullptr;
+    decltype(std::begin(m_array)) iterator;
+    if (std::size(m_array) < binarySearchThreshold) {
+        iterator = std::find_if(std::begin(m_array), std::end(m_array), [&parsedKey] (auto& pair) {
+            return pair.first == *parsedKey;
+        });
+        if (iterator == std::end(m_array))
+            return nullptr;
+    } else {
+        iterator = std::lower_bound(std::begin(m_array), std::end(m_array), *parsedKey, [] (auto& pair, auto& value) {
+            return pair.first < value;
+        });
+        if (iterator == std::end(m_array) || !(iterator->first == *parsedKey))
+            return nullptr;
+    }
+    return &iterator->second;
 }
 
 template<typename ArrayType> template<typename KeyArgument> inline auto SortedArrayMap<ArrayType>::get(const KeyArgument& key, const ValueType& defaultValue) const -> ValueType
@@ -152,11 +174,10 @@
 {
     using ElementType = typename std::remove_extent_t<ArrayType>;
     auto parsedKey = ElementType::parse(key);
-    // FIXME: We should enhance tryBinarySearch so it can deduce ElementType.
-    // FIXME: If the array's size is small enough, should do linear search since it is more efficient than binary search.
-    return parsedKey && tryBinarySearch<ElementType>(m_array, std::size(m_array), *parsedKey, [] (auto* element) {
-        return *element;
-    });
+    if (std::size(m_array) < binarySearchThreshold)
+        return std::find(std::begin(m_array), std::end(m_array), *parsedKey) != std::end(m_array);
+    auto iterator = std::lower_bound(std::begin(m_array), std::end(m_array), *parsedKey);
+    return iterator != std::end(m_array) && *iterator == *parsedKey;
 }
 
 constexpr int strcmpConstExpr(const char* a, const char* b)
@@ -173,8 +194,7 @@
     for (unsigned i = 0; i < length; ++i) {
         if (!literalWithNoUppercase[i])
             return false;
-        auto character = toASCIILower(characters[i]);
-        if (character != literalWithNoUppercase[i])
+        if (auto character = toASCIILower(characters[i]); character != literalWithNoUppercase[i])
             return character < literalWithNoUppercase[i];
     }
     return true;
@@ -187,6 +207,24 @@
     return lessThanASCIICaseFolding(string.characters16(), string.length(), literalWithNoUppercase);
 }
 
+template<typename CharacterType> inline bool lessThanASCIICaseFolding(const char* literalWithNoUppercase, const CharacterType* characters, unsigned length)
+{
+    for (unsigned i = 0; i < length; ++i) {
+        if (!literalWithNoUppercase[i])
+            return true;
+        if (auto character = toASCIILower(characters[i]); character != literalWithNoUppercase[i])
+            return literalWithNoUppercase[i] < character;
+    }
+    return false;
+}
+
+inline bool lessThanASCIICaseFolding(const char* literalWithNoUppercase, StringView string)
+{
+    if (string.is8Bit())
+        return lessThanASCIICaseFolding(literalWithNoUppercase, string.characters8(), string.length());
+    return lessThanASCIICaseFolding(literalWithNoUppercase, string.characters16(), string.length());
+}
+
 template<ASCIISubset subset> constexpr bool operator==(ComparableASCIISubsetLiteral<subset> a, ComparableASCIISubsetLiteral<subset> b)
 {
     return !strcmpConstExpr(a.literal.characters(), b.literal.characters());
@@ -207,6 +245,11 @@
     return codePointCompare(a.string, b.literal.characters()) < 0;
 }
 
+inline bool operator<(ComparableASCIILiteral a, ComparableStringView b)
+{
+    return codePointCompare(a.literal.characters(), b.string) < 0;
+}
+
 inline bool operator==(ComparableStringView a, ComparableLettersLiteral b)
 {
     return equalLettersIgnoringASCIICaseCommonWithoutLength(a.string, b.literal);
@@ -217,6 +260,11 @@
     return lessThanASCIICaseFolding(a.string, b.literal);
 }
 
+inline bool operator<(ComparableLettersLiteral a, ComparableStringView b)
+{
+    return lessThanASCIICaseFolding(a.literal, b.string);
+}
+
 inline bool operator==(ComparableStringView a, ComparableCaseFoldingASCIILiteral b)
 {
     return equalIgnoringASCIICase(a.string, b.literal);
@@ -227,6 +275,11 @@
     return lessThanASCIICaseFolding(a.string, b.literal);
 }
 
+inline bool operator<(ComparableCaseFoldingASCIILiteral a, ComparableStringView b)
+{
+    return lessThanASCIICaseFolding(a.literal, b.string);
+}
+
 template<typename OtherType> inline bool operator==(OtherType a, ComparableStringView b)
 {
     return b == a;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to