Procházet zdrojové kódy

LibJS: Speed up IndexedPropertyIterator by computing non-empty indices

This provides a huge speed-up for objects with large numbers as property
keys in some situation. Previously we would simply iterate from 0-<max>
and check if there's a non-empty value at each index - now we're being
smarter and compute a list of non-empty indices upfront, by checking
each value in the packed elements vector and appending the sparse
elements hashmap keys (for GenericIndexedPropertyStorage).

Consider this example, an object with a single own property, which is a
number increasing by a factor of 10 each iteration:

    for (let i = 0; i < 10; ++i) {
        const o = {[10 ** i]: "foo"};
        const start = Date.now();
        Object.getOwnPropertyNames(o);  // <-- IndexedPropertyIterator
        const end = Date.now();
        console.log(`${10 ** i} -> ${(end - start) / 1000}s`);
    }

Before this change:

    1 -> 0.0000s
    10 -> 0.0000s
    100 -> 0.0000s
    1000 -> 0.0000s
    10000 -> 0.0005s
    100000 -> 0.0039s
    1000000 -> 0.0295s
    10000000 -> 0.2489s
    100000000 -> 2.4758s
    1000000000 -> 25.5669s

After this change:

    1 -> 0.0000s
    10 -> 0.0000s
    100 -> 0.0000s
    1000 -> 0.0000s
    10000 -> 0.0000s
    100000 -> 0.0000s
    1000000 -> 0.0000s
    10000000 -> 0.0000s
    100000000 -> 0.0000s
    1000000000 -> 0.0000s

Fixes #3805.
Linus Groh před 4 roky
rodič
revize
a82c56f9f7

+ 46 - 10
Libraries/LibJS/Runtime/IndexedProperties.cpp

@@ -24,6 +24,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <AK/QuickSort.h>
 #include <LibJS/Runtime/Accessor.h>
 #include <LibJS/Runtime/IndexedProperties.h>
 
@@ -229,22 +230,16 @@ IndexedPropertyIterator::IndexedPropertyIterator(const IndexedProperties& indexe
     , m_index(staring_index)
     , m_skip_empty(skip_empty)
 {
-    while (m_skip_empty && m_index < m_indexed_properties.array_like_size()) {
-        if (m_indexed_properties.has_index(m_index))
-            break;
-        m_index++;
-    }
+    if (m_skip_empty)
+        skip_empty_indices();
 }
 
 IndexedPropertyIterator& IndexedPropertyIterator::operator++()
 {
     m_index++;
 
-    while (m_skip_empty && m_index < m_indexed_properties.array_like_size()) {
-        if (m_indexed_properties.has_index(m_index))
-            break;
-        m_index++;
-    };
+    if (m_skip_empty)
+        skip_empty_indices();
 
     return *this;
 }
@@ -266,6 +261,21 @@ ValueAndAttributes IndexedPropertyIterator::value_and_attributes(Object* this_ob
     return {};
 }
 
+void IndexedPropertyIterator::skip_empty_indices()
+{
+    auto indices = m_indexed_properties.indices();
+    if (indices.is_empty()) {
+        m_index = m_indexed_properties.array_like_size();
+        return;
+    }
+    for (auto i : indices) {
+        if (i < m_index)
+            continue;
+        m_index = i;
+        break;
+    }
+}
+
 Optional<ValueAndAttributes> IndexedProperties::get(Object* this_object, u32 index, bool evaluate_accessors) const
 {
     auto result = m_storage->get(index);
@@ -354,6 +364,32 @@ void IndexedProperties::set_array_like_size(size_t new_size)
     m_storage->set_array_like_size(new_size);
 }
 
+Vector<u32> IndexedProperties::indices() const
+{
+    Vector<u32> indices;
+    if (m_storage->is_simple_storage()) {
+        const auto& storage = static_cast<const SimpleIndexedPropertyStorage&>(*m_storage);
+        const auto& elements = storage.elements();
+        indices.ensure_capacity(storage.array_like_size());
+        for (size_t i = 0; i < elements.size(); ++i) {
+            if (!elements.at(i).is_empty())
+                indices.unchecked_append(i);
+        }
+    } else {
+        const auto& storage = static_cast<const GenericIndexedPropertyStorage&>(*m_storage);
+        const auto packed_elements = storage.packed_elements();
+        indices.ensure_capacity(storage.array_like_size());
+        for (size_t i = 0; i < packed_elements.size(); ++i) {
+            if (!packed_elements.at(i).value.is_empty())
+                indices.unchecked_append(i);
+        }
+        auto sparse_elements_keys = storage.sparse_elements().keys();
+        quick_sort(sparse_elements_keys);
+        indices.append(move(sparse_elements_keys));
+    }
+    return indices;
+}
+
 Vector<ValueAndAttributes> IndexedProperties::values_unordered() const
 {
     if (m_storage->is_simple_storage()) {

+ 4 - 0
Libraries/LibJS/Runtime/IndexedProperties.h

@@ -130,6 +130,8 @@ public:
     ValueAndAttributes value_and_attributes(Object* this_object, bool evaluate_accessors = true);
 
 private:
+    void skip_empty_indices();
+
     const IndexedProperties& m_indexed_properties;
     u32 m_index;
     bool m_skip_empty;
@@ -164,6 +166,8 @@ public:
     size_t array_like_size() const { return m_storage->array_like_size(); }
     void set_array_like_size(size_t);
 
+    Vector<u32> indices() const;
+
     Vector<ValueAndAttributes> values_unordered() const;
 
 private: