diff options
Diffstat (limited to 'source/blender/functions')
-rw-r--r-- | source/blender/functions/CMakeLists.txt | 18 | ||||
-rw-r--r-- | source/blender/functions/FN_generic_virtual_array.hh | 46 | ||||
-rw-r--r-- | source/blender/functions/FN_multi_function_parallel.hh | 39 | ||||
-rw-r--r-- | source/blender/functions/intern/field.cc | 29 | ||||
-rw-r--r-- | source/blender/functions/intern/generic_virtual_array.cc | 39 | ||||
-rw-r--r-- | source/blender/functions/intern/multi_function_parallel.cc | 109 | ||||
-rw-r--r-- | source/blender/functions/tests/FN_multi_function_test.cc | 27 |
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 |