Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/functions')
-rw-r--r--source/blender/functions/CMakeLists.txt18
-rw-r--r--source/blender/functions/FN_generic_virtual_array.hh46
-rw-r--r--source/blender/functions/FN_multi_function_parallel.hh39
-rw-r--r--source/blender/functions/intern/field.cc29
-rw-r--r--source/blender/functions/intern/generic_virtual_array.cc39
-rw-r--r--source/blender/functions/intern/multi_function_parallel.cc109
-rw-r--r--source/blender/functions/tests/FN_multi_function_test.cc27
7 files changed, 297 insertions, 10 deletions
diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt
index 3c27e9d5e19..856668f01d7 100644
--- a/source/blender/functions/CMakeLists.txt
+++ b/source/blender/functions/CMakeLists.txt
@@ -34,6 +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_procedure.cc
intern/multi_function_procedure_builder.cc
intern/multi_function_procedure_executor.cc
@@ -54,6 +55,7 @@ set(SRC
FN_multi_function_data_type.hh
FN_multi_function_param_type.hh
FN_multi_function_params.hh
+ FN_multi_function_parallel.hh
FN_multi_function_procedure.hh
FN_multi_function_procedure_builder.hh
FN_multi_function_procedure_executor.hh
@@ -64,6 +66,22 @@ set(LIB
bf_blenlib
)
+if(WITH_TBB)
+ add_definitions(-DWITH_TBB)
+ if(WIN32)
+ # TBB includes Windows.h which will define min/max macros
+ # that will collide with the stl versions.
+ add_definitions(-DNOMINMAX)
+ endif()
+ list(APPEND INC_SYS
+ ${TBB_INCLUDE_DIRS}
+ )
+
+ list(APPEND LIB
+ ${TBB_LIBRARIES}
+ )
+endif()
+
blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
diff --git a/source/blender/functions/FN_generic_virtual_array.hh b/source/blender/functions/FN_generic_virtual_array.hh
index f429243e2de..b883db6fa31 100644
--- a/source/blender/functions/FN_generic_virtual_array.hh
+++ b/source/blender/functions/FN_generic_virtual_array.hh
@@ -911,4 +911,50 @@ template<typename T> class GVMutableArray_Typed {
}
};
+class GVArray_For_SlicedGVArray : public GVArray {
+ protected:
+ const GVArray &varray_;
+ int64_t offset_;
+
+ public:
+ GVArray_For_SlicedGVArray(const GVArray &varray, const IndexRange slice)
+ : GVArray(varray.type(), slice.size()), varray_(varray), offset_(slice.start())
+ {
+ BLI_assert(slice.one_after_last() <= varray.size());
+ }
+
+ void get_impl(const int64_t index, void *r_value) const override;
+ void get_to_uninitialized_impl(const int64_t index, void *r_value) const override;
+};
+
+/**
+ * Utility class to create the "best" sliced virtual array.
+ */
+class GVArray_Slice {
+ private:
+ const GVArray *varray_;
+ /* Of these optional virtual arrays, at most one is constructed at any time. */
+ std::optional<GVArray_For_GSpan> varray_span_;
+ std::optional<GVArray_For_SingleValue> varray_single_;
+ std::optional<GVArray_For_SlicedGVArray> varray_any_;
+
+ public:
+ GVArray_Slice(const GVArray &varray, const IndexRange slice);
+
+ const GVArray &operator*()
+ {
+ return *varray_;
+ }
+
+ const GVArray *operator->()
+ {
+ return varray_;
+ }
+
+ operator const GVArray &()
+ {
+ return *varray_;
+ }
+};
+
} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_parallel.hh b/source/blender/functions/FN_multi_function_parallel.hh
new file mode 100644
index 00000000000..84c57efd434
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_parallel.hh
@@ -0,0 +1,39 @@
+/*
+ * 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/intern/field.cc b/source/blender/functions/intern/field.cc
index fa7dea97b7f..f680c7ba5ff 100644
--- a/source/blender/functions/intern/field.cc
+++ b/source/blender/functions/intern/field.cc
@@ -18,9 +18,13 @@
#include "BLI_multi_value_map.hh"
#include "BLI_set.hh"
#include "BLI_stack.hh"
+#include "BLI_timeit.hh"
#include "BLI_vector_set.hh"
#include "FN_field.hh"
+#include "FN_multi_function_parallel.hh"
+
+#include "FN_field.hh"
namespace blender::fn {
@@ -183,8 +187,8 @@ static void build_multi_function_procedure_for_fields(MFProcedure &procedure,
const Span<GField> operation_inputs = operation.inputs();
if (field_with_index.current_input_index < operation_inputs.size()) {
- /* Not all inputs are handled yet. Push the next input field to the stack and increment the
- * input index. */
+ /* Not all inputs are handled yet. Push the next input field to the stack and increment
+ * the input index. */
fields_to_check.push({operation_inputs[field_with_index.current_input_index]});
field_with_index.current_input_index++;
}
@@ -248,8 +252,8 @@ struct PartiallyInitializedArray : NonCopyable, NonMovable {
};
/**
- * Evaluate fields in the given context. If possible, multiple fields should be evaluated together,
- * because that can be more efficient when they share common sub-fields.
+ * Evaluate fields in the given context. If possible, multiple fields should be evaluated
+ * together, because that can be more efficient when they share common sub-fields.
*
* \param scope: The resource scope that owns data that makes up the output virtual arrays. Make
* sure the scope is not destructed when the output virtual arrays are still used.
@@ -271,6 +275,8 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
const FieldContext &context,
Span<GVMutableArray *> dst_varrays)
{
+ SCOPED_TIMER(__func__);
+
Vector<const GVArray *> r_varrays(fields_to_evaluate.size(), nullptr);
const int array_size = mask.min_array_size();
@@ -290,8 +296,8 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
Vector<const GVArray *> field_context_inputs = get_field_context_inputs(
scope, mask, context, field_tree_info.deduplicated_field_inputs);
- /* Finish fields that output an input varray directly. For those we don't have to do any further
- * processing. */
+ /* Finish fields that output an input varray directly. For those we don't have to do any
+ * further processing. */
for (const int out_index : fields_to_evaluate.index_range()) {
const GFieldRef &field = fields_to_evaluate[out_index];
if (!field.node().is_input()) {
@@ -334,7 +340,10 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
build_multi_function_procedure_for_fields(
procedure, scope, field_tree_info, varying_fields_to_evaluate);
MFProcedureExecutor procedure_executor{"Procedure", procedure};
- MFParamsBuilder mf_params{procedure_executor, array_size};
+ fn::ParallelMultiFunction parallel_fn{procedure_executor, 20000};
+ const MultiFunction &fn_to_execute = procedure_executor;
+
+ MFParamsBuilder mf_params{fn_to_execute, array_size};
MFContextBuilder mf_context;
/* Provide inputs to the procedure executor. */
@@ -376,7 +385,7 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
mf_params.add_uninitialized_single_output(span);
}
- procedure_executor.call(mask, mf_params, mf_context);
+ fn_to_execute.call(mask, mf_params, mf_context);
}
/* Evaluate constant fields if necessary. */
@@ -419,8 +428,8 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
procedure_executor.call(IndexRange(1), mf_params, mf_context);
}
- /* Copy data to supplied destination arrays if necessary. In some cases the evaluation above has
- * written the computed data in the right place already. */
+ /* Copy data to supplied destination arrays if necessary. In some cases the evaluation above
+ * has written the computed data in the right place already. */
if (!dst_varrays.is_empty()) {
for (const int out_index : fields_to_evaluate.index_range()) {
GVMutableArray *output_varray = get_dst_varray_if_available(out_index);
diff --git a/source/blender/functions/intern/generic_virtual_array.cc b/source/blender/functions/intern/generic_virtual_array.cc
index bd033a429de..a3096417212 100644
--- a/source/blender/functions/intern/generic_virtual_array.cc
+++ b/source/blender/functions/intern/generic_virtual_array.cc
@@ -387,4 +387,43 @@ void GVMutableArray_GSpan::disable_not_applied_warning()
show_not_saved_warning_ = false;
}
+/* --------------------------------------------------------------------
+ * GVArray_For_SlicedGVArray.
+ */
+
+void GVArray_For_SlicedGVArray::get_impl(const int64_t index, void *r_value) const
+{
+ varray_.get(index + offset_, r_value);
+}
+
+void GVArray_For_SlicedGVArray::get_to_uninitialized_impl(const int64_t index, void *r_value) const
+{
+ varray_.get_to_uninitialized(index + offset_, r_value);
+}
+
+/* --------------------------------------------------------------------
+ * GVArray_Slice.
+ */
+
+GVArray_Slice::GVArray_Slice(const GVArray &varray, const IndexRange slice)
+{
+ const CPPType &type = varray.type();
+ if (varray.is_span()) {
+ const GSpan span = varray.get_internal_span();
+ varray_span_.emplace(span.slice(slice.start(), slice.size()));
+ varray_ = &*varray_span_;
+ }
+ else if (varray.is_single()) {
+ BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
+ varray_->get_internal_single_to_uninitialized(buffer);
+ varray_single_.emplace(type, slice.size(), buffer);
+ type.destruct(buffer);
+ varray_ = &*varray_single_;
+ }
+ else {
+ varray_any_.emplace(varray, slice);
+ varray_ = &*varray_any_;
+ }
+}
+
} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_parallel.cc b/source/blender/functions/intern/multi_function_parallel.cc
new file mode 100644
index 00000000000..6843c4a233b
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_parallel.cc
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+#include "FN_multi_function_parallel.hh"
+
+#include "BLI_task.hh"
+
+#include <mutex>
+
+namespace blender::fn {
+
+ParallelMultiFunction::ParallelMultiFunction(const MultiFunction &fn, const int64_t grain_size)
+ : fn_(fn), grain_size_(grain_size)
+{
+ this->set_signature(&fn.signature());
+
+ threading_supported_ = true;
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ if (param_type.data_type().category() == MFDataType::Vector) {
+ threading_supported_ = false;
+ break;
+ }
+ }
+}
+
+void ParallelMultiFunction::call(IndexMask mask, MFParams params, MFContext context) const
+{
+ if (mask.size() <= grain_size_ || !threading_supported_) {
+ fn_.call(mask, params, context);
+ return;
+ }
+
+ threading::parallel_for(mask.index_range(), grain_size_, [&](const IndexRange range) {
+ const int size = range.size();
+ IndexMask original_sub_mask{mask.indices().slice(range)};
+ const int64_t offset = original_sub_mask.indices().first();
+ const int64_t slice_size = original_sub_mask.indices().last() - offset + 1;
+ const IndexRange slice_range{offset, slice_size};
+ IndexMask sub_mask;
+ Vector<int64_t> sub_mask_indices;
+ if (original_sub_mask.is_range()) {
+ sub_mask = IndexMask(size);
+ }
+ else {
+ sub_mask_indices.resize(size);
+ for (const int i : IndexRange(size)) {
+ sub_mask_indices[i] = original_sub_mask[i] - offset;
+ }
+ sub_mask = sub_mask_indices.as_span();
+ }
+
+ MFParamsBuilder sub_params{fn_, sub_mask.min_array_size()};
+ ResourceScope scope;
+ // static std::mutex mutex;
+ // {
+ // std::lock_guard lock{mutex};
+ // std::cout << range << " " << sub_mask.min_array_size() << "\n";
+ // }
+
+ for (const int param_index : fn_.param_indices()) {
+ const MFParamType param_type = fn_.param_type(param_index);
+ switch (param_type.category()) {
+ case MFParamType::SingleInput: {
+ const GVArray &varray = params.readonly_single_input(param_index);
+ const GVArray &sliced_varray = scope.construct<GVArray_Slice>(
+ "sliced varray", varray, slice_range);
+ sub_params.add_readonly_single_input(sliced_varray);
+ break;
+ }
+ case MFParamType::SingleMutable: {
+ const GMutableSpan span = params.single_mutable(param_index);
+ const GMutableSpan sliced_span = span.slice(slice_range.start(), slice_range.size());
+ sub_params.add_single_mutable(sliced_span);
+ break;
+ }
+ case MFParamType::SingleOutput: {
+ const GMutableSpan span = params.uninitialized_single_output(param_index);
+ const GMutableSpan sliced_span = span.slice(slice_range.start(), slice_range.size());
+ sub_params.add_uninitialized_single_output(sliced_span);
+ break;
+ }
+ case MFParamType::VectorInput:
+ case MFParamType::VectorMutable:
+ case MFParamType::VectorOutput: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ fn_.call(sub_mask, sub_params, context);
+ });
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/tests/FN_multi_function_test.cc b/source/blender/functions/tests/FN_multi_function_test.cc
index 91c72a51dd6..9deeaf8d3bd 100644
--- a/source/blender/functions/tests/FN_multi_function_test.cc
+++ b/source/blender/functions/tests/FN_multi_function_test.cc
@@ -2,8 +2,11 @@
#include "testing/testing.h"
+#include "BLI_timeit.hh"
+
#include "FN_multi_function.hh"
#include "FN_multi_function_builder.hh"
+#include "FN_multi_function_parallel.hh"
#include "FN_multi_function_test_common.hh"
namespace blender::fn::tests {
@@ -328,5 +331,29 @@ TEST(multi_function, CustomMF_Convert)
EXPECT_EQ(outputs[2], 9);
}
+TEST(multi_function, Parallel)
+{
+ CustomMF_SI_SI_SO<float, float, float> add_fn{
+ "add", [](float a, float b) { return std::tan(std::sin(a)) * std::tanh(std::cos(b)); }};
+ ParallelMultiFunction parallel_fn{add_fn, int64_t(1e5)};
+ const MultiFunction &fn_to_evaluate = parallel_fn;
+
+ const int amount = 1e8;
+ Array<float> inputs_a(amount, 1);
+ Array<float> inputs_b(amount, 1);
+ Array<float> outputs(amount, 1);
+
+ for (int i = 0; i < 10; i++) {
+ SCOPED_TIMER(__func__);
+ MFParamsBuilder params(fn_to_evaluate, amount);
+ params.add_readonly_single_input(inputs_a.as_span());
+ params.add_readonly_single_input(inputs_b.as_span());
+ params.add_uninitialized_single_output(outputs.as_mutable_span());
+
+ MFContextBuilder context;
+ fn_to_evaluate.call(IndexRange(amount), params, context);
+ }
+}
+
} // namespace
} // namespace blender::fn::tests