瀏覽代碼

LibWeb: Use copy-on-write to make cloning a FormattingState much cheaper

Instead of making a full copy of every NodeState when cloning a
FormattingState, we make NodeState ref-counted and implement a basic
copy-on-write mechanism.

FormattingState::get_mutable() now makes a deep copy of the NodeState
when first accessed *if* it is shared with other FormattingStates.
Andreas Kling 3 年之前
父節點
當前提交
aa72ebf7aa
共有 2 個文件被更改,包括 36 次插入18 次删除
  1. 17 0
      Userland/Libraries/LibWeb/Layout/FormattingState.cpp
  2. 19 18
      Userland/Libraries/LibWeb/Layout/FormattingState.h

+ 17 - 0
Userland/Libraries/LibWeb/Layout/FormattingState.cpp

@@ -9,6 +9,23 @@
 
 namespace Web::Layout {
 
+FormattingState::NodeState& FormattingState::get_mutable(NodeWithStyleAndBoxModelMetrics const& box)
+{
+    auto state = nodes.ensure(&box, [] { return adopt_ref(*new NodeState); });
+    // CoW if ref_count > 2 (1 for the entry in `this->nodes`, 1 for the `state` local in this function)
+    if (state->ref_count > 2) {
+        state = adopt_ref(*new NodeState { *state });
+        state->ref_count = 1;
+        nodes.set(&box, state);
+    }
+    return state;
+}
+
+FormattingState::NodeState const& FormattingState::get(NodeWithStyleAndBoxModelMetrics const& box) const
+{
+    return *const_cast<FormattingState&>(*this).nodes.ensure(&box, [] { return adopt_ref(*new NodeState); });
+}
+
 void FormattingState::commit()
 {
     for (auto& it : nodes) {

+ 19 - 18
Userland/Libraries/LibWeb/Layout/FormattingState.h

@@ -62,30 +62,31 @@ struct FormattingState {
                 overflow_data = Layout::Box::OverflowData {};
             return *overflow_data;
         }
+
+        // NOTE: NodeState is ref-counted and accessed via copy-on-write helpers below.
+        size_t ref_count { 1 };
+        void ref()
+        {
+            VERIFY(ref_count);
+            ++ref_count;
+        }
+        void unref()
+        {
+            VERIFY(ref_count);
+            if (!--ref_count)
+                delete this;
+        }
     };
 
     void commit();
 
-    FormattingState clone() const
-    {
-        FormattingState new_state;
-        for (auto& it : nodes) {
-            new_state.nodes.set(it.key, make<NodeState>(*it.value));
-        }
-        return new_state;
-    }
-
-    NodeState& get_mutable(NodeWithStyleAndBoxModelMetrics const& box)
-    {
-        return *nodes.ensure(&box, [] { return make<NodeState>(); });
-    }
+    // NOTE: get_mutable() will CoW the NodeState if it's shared with another FormattingContext.
+    NodeState& get_mutable(NodeWithStyleAndBoxModelMetrics const&);
 
-    NodeState const& get(NodeWithStyleAndBoxModelMetrics const& box) const
-    {
-        return const_cast<FormattingState&>(*this).get_mutable(box);
-    }
+    // NOTE: get() will not CoW the NodeState.
+    NodeState const& get(NodeWithStyleAndBoxModelMetrics const&) const;
 
-    HashMap<NodeWithStyleAndBoxModelMetrics const*, NonnullOwnPtr<NodeState>> nodes;
+    HashMap<NodeWithStyleAndBoxModelMetrics const*, NonnullRefPtr<NodeState>> nodes;
 };
 
 Gfx::FloatRect absolute_content_rect(Box const&, FormattingState const&);