瀏覽代碼

LibJS: Use a premade shape when creating iterator result objects

Instead of going through the steps of creating an empty new object,
and adding two properties ("value" and "done") to it, we can pre-bake
a shape object and cache the property offsets.

This makes creating iterator result objects in the runtime much faster.

47% speedup on this microbenchmark:

    function go(a) {
        for (const p of a) {
        }
    }
    const a = [];
    a.length = 1_000_000;
    go(a);
Andreas Kling 1 年之前
父節點
當前提交
f47a14b9d6

+ 11 - 0
Userland/Libraries/LibJS/Runtime/Intrinsics.cpp

@@ -190,6 +190,16 @@ ThrowCompletionOr<void> Intrinsics::initialize_intrinsics(Realm& realm)
     m_new_ordinary_function_prototype_object_shape->set_prototype_without_transition(m_object_prototype);
     m_new_ordinary_function_prototype_object_shape->add_property_without_transition(vm.names.constructor, Attribute::Writable | Attribute::Configurable);
 
+    // OPTIMIZATION: A lot of runtime algorithms create an "iterator result" object.
+    //               We pre-bake a shape for these objects and remember the property offsets.
+    //               This allows us to construct them very quickly.
+    m_iterator_result_object_shape = heap().allocate_without_realm<Shape>(realm);
+    m_iterator_result_object_shape->set_prototype_without_transition(m_object_prototype);
+    m_iterator_result_object_shape->add_property_without_transition(vm.names.value, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable);
+    m_iterator_result_object_shape->add_property_without_transition(vm.names.done, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable);
+    m_iterator_result_object_value_offset = m_iterator_result_object_shape->lookup(vm.names.value.to_string_or_symbol()).value().offset;
+    m_iterator_result_object_done_offset = m_iterator_result_object_shape->lookup(vm.names.done.to_string_or_symbol()).value().offset;
+
     // Normally Heap::allocate() takes care of this, but these are allocated via allocate_without_realm().
     m_function_prototype->initialize(realm);
     m_object_prototype->initialize(realm);
@@ -358,6 +368,7 @@ void Intrinsics::visit_edges(Visitor& visitor)
     visitor.visit(m_empty_object_shape);
     visitor.visit(m_new_object_shape);
     visitor.visit(m_new_ordinary_function_prototype_object_shape);
+    visitor.visit(m_iterator_result_object_shape);
     visitor.visit(m_proxy_constructor);
     visitor.visit(m_async_from_sync_iterator_prototype);
     visitor.visit(m_async_generator_prototype);

+ 8 - 0
Userland/Libraries/LibJS/Runtime/Intrinsics.h

@@ -23,6 +23,10 @@ public:
     NonnullGCPtr<Shape> new_object_shape() { return *m_new_object_shape; }
     NonnullGCPtr<Shape> new_ordinary_function_prototype_object_shape() { return *m_new_ordinary_function_prototype_object_shape; }
 
+    [[nodiscard]] NonnullGCPtr<Shape> iterator_result_object_shape() { return *m_iterator_result_object_shape; }
+    [[nodiscard]] u32 iterator_result_object_value_offset() { return m_iterator_result_object_value_offset; }
+    [[nodiscard]] u32 iterator_result_object_done_offset() { return m_iterator_result_object_done_offset; }
+
     // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype
     NonnullGCPtr<ProxyConstructor> proxy_constructor() { return *m_proxy_constructor; }
 
@@ -123,6 +127,10 @@ private:
     GCPtr<Shape> m_new_object_shape;
     GCPtr<Shape> m_new_ordinary_function_prototype_object_shape;
 
+    GCPtr<Shape> m_iterator_result_object_shape;
+    u32 m_iterator_result_object_value_offset { 0 };
+    u32 m_iterator_result_object_done_offset { 0 };
+
     // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype
     GCPtr<ProxyConstructor> m_proxy_constructor;
 

+ 3 - 3
Userland/Libraries/LibJS/Runtime/Iterator.cpp

@@ -264,13 +264,13 @@ NonnullGCPtr<Object> create_iterator_result_object(VM& vm, Value value, bool don
     auto& realm = *vm.current_realm();
 
     // 1. Let obj be OrdinaryObjectCreate(%Object.prototype%).
-    auto object = Object::create(realm, realm.intrinsics().object_prototype());
+    auto object = Object::create_with_premade_shape(realm.intrinsics().iterator_result_object_shape());
 
     // 2. Perform ! CreateDataPropertyOrThrow(obj, "value", value).
-    MUST(object->create_data_property_or_throw(vm.names.value, value));
+    object->put_direct(realm.intrinsics().iterator_result_object_value_offset(), value);
 
     // 3. Perform ! CreateDataPropertyOrThrow(obj, "done", done).
-    MUST(object->create_data_property_or_throw(vm.names.done, Value(done)));
+    object->put_direct(realm.intrinsics().iterator_result_object_done_offset(), Value(done));
 
     // 4. Return obj.
     return object;

+ 5 - 0
Userland/Libraries/LibJS/Runtime/Object.cpp

@@ -37,6 +37,11 @@ NonnullGCPtr<Object> Object::create(Realm& realm, Object* prototype)
     return realm.heap().allocate<Object>(realm, ConstructWithPrototypeTag::Tag, *prototype);
 }
 
+NonnullGCPtr<Object> Object::create_with_premade_shape(Shape& shape)
+{
+    return shape.heap().allocate<Object>(shape.realm(), shape);
+}
+
 Object::Object(GlobalObjectTag, Realm& realm, MayInterfereWithIndexedPropertyAccess may_interfere_with_indexed_property_access)
     : m_may_interfere_with_indexed_property_access(may_interfere_with_indexed_property_access == MayInterfereWithIndexedPropertyAccess::Yes)
 {

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Object.h

@@ -58,6 +58,7 @@ class Object : public Cell {
 
 public:
     static NonnullGCPtr<Object> create(Realm&, Object* prototype);
+    static NonnullGCPtr<Object> create_with_premade_shape(Shape&);
 
     virtual void initialize(Realm&) override;
     virtual ~Object();