[Bf-blender-cvs] [658fd8df0bd] master: Geometry Nodes: refactor multi-threading in field evaluation

Jacques Lucke noreply at git.blender.org
Fri Nov 26 11:06:23 CET 2021


Commit: 658fd8df0bd2427cd77e7fc4bcca8a102f67b626
Author: Jacques Lucke
Date:   Fri Nov 26 11:05:47 2021 +0100
Branches: master
https://developer.blender.org/rB658fd8df0bd2427cd77e7fc4bcca8a102f67b626

Geometry Nodes: refactor multi-threading in field evaluation

Previously, there was a fixed grain size for all multi-functions. That was
not sufficient because some functions could benefit a lot from smaller
grain sizes.

This refactors adds a new `MultiFunction::call_auto` method which has the
same effect as just calling `MultiFunction::call` but additionally figures
out how to execute the specific multi-function efficiently. It determines
a good grain size and decides whether the mask indices should be shifted
or not.

Most multi-function evaluations benefit from this, but medium sized work
loads (1000 - 50000 elements) benefit from it the most. Especially when
expensive multi-functions (e.g. noise) is involved. This is because for
smaller work loads, threading is rarely used and for larger work loads
threading worked fine before already.

With this patch, multi-functions can specify execution hints, that allow
the caller to execute it most efficiently. These execution hints still
have to be added to more functions.

Some performance measurements of a field evaluation involving noise and
math nodes, ordered by the number of elements being evaluated:
```
1,000,000: 133 ms   -> 120 ms
  100,000:  30 ms   ->  18 ms
   10,000:  20 ms   ->   2.7 ms
    1,000:   4 ms   ->   0.5 ms
      100:   0.5 ms ->   0.4 ms
```

===================================================================

M	source/blender/functions/CMakeLists.txt
M	source/blender/functions/FN_multi_function.hh
D	source/blender/functions/FN_multi_function_parallel.hh
M	source/blender/functions/FN_multi_function_params.hh
M	source/blender/functions/FN_multi_function_procedure_executor.hh
M	source/blender/functions/intern/field.cc
M	source/blender/functions/intern/multi_function.cc
D	source/blender/functions/intern/multi_function_parallel.cc
A	source/blender/functions/intern/multi_function_params.cc
M	source/blender/functions/intern/multi_function_procedure_executor.cc
M	source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc
M	source/blender/nodes/shader/nodes/node_shader_tex_noise.cc
M	source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc

===================================================================

diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt
index 54670c0d1b3..63c11164275 100644
--- a/source/blender/functions/CMakeLists.txt
+++ b/source/blender/functions/CMakeLists.txt
@@ -34,7 +34,7 @@ set(SRC
   intern/generic_virtual_vector_array.cc
   intern/multi_function.cc
   intern/multi_function_builder.cc
-  intern/multi_function_parallel.cc
+  intern/multi_function_params.cc
   intern/multi_function_procedure.cc
   intern/multi_function_procedure_builder.cc
   intern/multi_function_procedure_executor.cc
@@ -54,7 +54,6 @@ set(SRC
   FN_multi_function_builder.hh
   FN_multi_function_context.hh
   FN_multi_function_data_type.hh
-  FN_multi_function_parallel.hh
   FN_multi_function_param_type.hh
   FN_multi_function_params.hh
   FN_multi_function_procedure.hh
diff --git a/source/blender/functions/FN_multi_function.hh b/source/blender/functions/FN_multi_function.hh
index 3059fe59ca7..af60e54808e 100644
--- a/source/blender/functions/FN_multi_function.hh
+++ b/source/blender/functions/FN_multi_function.hh
@@ -60,6 +60,7 @@ class MultiFunction {
   {
   }
 
+  void call_auto(IndexMask mask, MFParams params, MFContext context) const;
   virtual void call(IndexMask mask, MFParams params, MFContext context) const = 0;
 
   virtual uint64_t hash() const
@@ -110,6 +111,31 @@ class MultiFunction {
     return *signature_ref_;
   }
 
+  /**
+   * Information about how the multi-function behaves that help a caller to execute it efficiently.
+   */
+  struct ExecutionHints {
+    /**
+     * Suggested minimum workload under which multi-threading does not really help.
+     * This should be lowered when the multi-function is doing something computationally expensive.
+     */
+    int64_t min_grain_size = 10000;
+    /**
+     * Indicates that the multi-function will allocate an array large enough to hold all indices
+     * passed in as mask. This tells the caller that it would be preferable to pass in smaller
+     * indices. Also maybe the full mask should be split up into smaller segments to decrease peak
+     * memory usage.
+     */
+    bool allocates_array = false;
+    /**
+     * Tells the caller that every execution takes about the same time. This helps making a more
+     * educated guess about a good grain size.
+     */
+    bool uniform_execution_time = true;
+  };
+
+  ExecutionHints execution_hints() const;
+
  protected:
   /* Make the function use the given signature. This should be called once in the constructor of
    * child classes. No copy of the signature is made, so the caller has to make sure that the
@@ -121,6 +147,8 @@ class MultiFunction {
     BLI_assert(signature != nullptr);
     signature_ref_ = signature;
   }
+
+  virtual ExecutionHints get_execution_hints() const;
 };
 
 inline MFParamsBuilder::MFParamsBuilder(const MultiFunction &fn, int64_t mask_size)
diff --git a/source/blender/functions/FN_multi_function_parallel.hh b/source/blender/functions/FN_multi_function_parallel.hh
deleted file mode 100644
index 84c57efd434..00000000000
--- a/source/blender/functions/FN_multi_function_parallel.hh
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#pragma once
-
-/** \file
- * \ingroup fn
- */
-
-#include "FN_multi_function.hh"
-
-namespace blender::fn {
-
-class ParallelMultiFunction : public MultiFunction {
- private:
-  const MultiFunction &fn_;
-  const int64_t grain_size_;
-  bool threading_supported_;
-
- public:
-  ParallelMultiFunction(const MultiFunction &fn, const int64_t grain_size);
-
-  void call(IndexMask mask, MFParams params, MFContext context) const override;
-};
-
-}  // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh
index 5c7e75230f3..f4ddc4f2881 100644
--- a/source/blender/functions/FN_multi_function_params.hh
+++ b/source/blender/functions/FN_multi_function_params.hh
@@ -25,6 +25,8 @@
  * the function. `MFParams` is then used inside the called function to access the parameters.
  */
 
+#include <mutex>
+
 #include "BLI_resource_scope.hh"
 
 #include "FN_generic_pointer.hh"
@@ -45,6 +47,9 @@ class MFParamsBuilder {
   Vector<const GVVectorArray *> virtual_vector_arrays_;
   Vector<GVectorArray *> vector_arrays_;
 
+  std::mutex mutex_;
+  Vector<std::pair<int, GMutableSpan>> dummy_output_spans_;
+
   friend class MFParams;
 
   MFParamsBuilder(const MFSignature &signature, const IndexMask mask)
@@ -62,8 +67,8 @@ class MFParamsBuilder {
 
   template<typename T> void add_readonly_single_input_value(T value, StringRef expected_name = "")
   {
-    T *value_ptr = &scope_.add_value<T>(std::move(value));
-    this->add_readonly_single_input(value_ptr, expected_name);
+    this->add_readonly_single_input(VArray<T>::ForSingle(std::move(value), min_array_size_),
+                                    expected_name);
   }
   template<typename T> void add_readonly_single_input(const T *value, StringRef expected_name = "")
   {
@@ -254,20 +259,12 @@ class MFParams {
     this->assert_correct_param(param_index, name, MFParamType::SingleOutput);
     int data_index = builder_->signature_->data_index(param_index);
     GMutableSpan span = builder_->mutable_spans_[data_index];
-    if (span.is_empty()) {
-      /* The output is ignored by the caller, but the multi-function does not handle this case. So
-       * create a temporary buffer that the multi-function can write to. */
-      const CPPType &type = span.type();
-      void *buffer = builder_->scope_.linear_allocator().allocate(
-          builder_->min_array_size_ * type.size(), type.alignment());
-      if (!type.is_trivially_destructible()) {
-        /* Make sure the temporary elements will be destructed in the end. */
-        builder_->scope_.add_destruct_call(
-            [&type, buffer, mask = builder_->mask_]() { type.destruct_indices(buffer, mask); });
-      }
-      span = GMutableSpan{type, buffer, builder_->min_array_size_};
+    if (!span.is_empty()) {
+      return span;
     }
-    return span;
+    /* The output is ignored by the caller, but the multi-function does not handle this case. So
+     * create a temporary buffer that the multi-function can write to. */
+    return this->ensure_dummy_single_output(data_index);
   }
 
   /**
@@ -356,6 +353,8 @@ class MFParams {
     }
 #endif
   }
+
+  GMutableSpan ensure_dummy_single_output(int data_index);
 };
 
 }  // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_procedure_executor.hh b/source/blender/functions/FN_multi_function_procedure_executor.hh
index b12e3a91210..409a031e7ed 100644
--- a/source/blender/functions/FN_multi_function_procedure_executor.hh
+++ b/source/blender/functions/FN_multi_function_procedure_executor.hh
@@ -34,6 +34,9 @@ class MFProcedureExecutor : public MultiFunction {
   MFProcedureExecutor(const MFProcedure &procedure);
 
   void call(IndexMask mask, MFParams params, MFContext context) const override;
+
+ private:
+  ExecutionHints get_execution_hints() const override;
 };
 
 }  // namespace blender::fn
diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc
index 7934490a6d9..297df3c15cf 100644
--- a/source/blender/functions/intern/field.cc
+++ b/source/blender/functions/intern/field.cc
@@ -21,7 +21,6 @@
 #include "BLI_vector_set.hh"
 
 #include "FN_field.hh"
-#include "FN_multi_function_parallel.hh"
 
 namespace blender::fn {
 
@@ -358,13 +357,8 @@ Vector<GVArray> evaluate_fields(ResourceScope &scope,
     build_multi_function_procedure_for_fields(
         procedure, scope, field_tree_info, varying_fields_to_evaluate);
     MFProcedureExecutor procedure_executor{procedure};
-    /* Add multi threading capabilities to the field evaluation. */
-    const int grain_size = 10000;
-    fn::ParallelMultiFunction parallel_procedure_executor{procedure_executor, grain_size};
-    /* Utility variable to make easy to switch the executor. */
-    const MultiFunction &executor_fn = parallel_procedure_executor;
 
-    MFParamsBuilder mf_params{executor_fn, &mask};
+    MFParamsBuilder mf_params{procedure_executor, &mask};
     MFContextBuilder mf_context;
 
     /* Provide inputs to the procedure executor. */
@@ -405,7 +399,7 @@ Vector<GVArray> evaluate_fields(ResourceScope &scope,
       mf_params.add_uninitialized_single_output(span);
     }
 
-    executor_fn.call(mask, mf_params, mf_context);
+    procedure_executor.call_auto(mask, mf_params, mf_context);
   }
 
   /* Evaluate constant fields if necessary. */
diff --git a/source/blender/functions/intern/multi_function.cc b/source/blender/functions/intern/multi_function.cc
index ee2c69068db..3e5539d4248 100644
--- a/source/blender/functions/intern/multi_function.cc
+++ b/source/blender/functions/intern/multi_function.cc
@@ -16,8 +16,141 @@
 
 #include "FN_multi_function.hh"
 
+#include "BLI_task.hh"
+#include "BLI_threads.h"
+
 namespace blender::fn {
 
+using ExecutionHints = MultiFunction::ExecutionHints;
+
+ExecutionHints MultiFunction::execution_hints() const
+{
+  return this->get_execution_hints();
+}
+
+ExecutionHints MultiFunction::get_execution_hints() const
+{
+  return ExecutionHints{};
+}
+
+static bool supports_threading_by_slicing_params(const MultiFunction &fn)
+{
+  for (const int i : fn.param_indices()) {
+    const MFParamType param_type = fn.param_type(i);
+    if (ELEM(param_type.interface_type(),
+             MFParamType::InterfaceType::Mutable,
+             MFParamType::InterfaceType::Outp

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list