Compare commits
227 Commits
temp-T1024
...
temp-paral
Author | SHA1 | Date | |
---|---|---|---|
4469102925 | |||
0eb570cbbb | |||
e3594c8e65 | |||
bcae5507b8 | |||
29704854df | |||
2920a569b5 | |||
015bde6145 | |||
8d19ceaee7 | |||
d62cf98164 | |||
655223e4c6 | |||
068f012221 | |||
54cc128a31 | |||
fa5a9b0f6a | |||
cffd4a7ccf | |||
2012469acc | |||
713da0fcc4 | |||
8ba889a4be | |||
5cd7e557ae | |||
584af77101 | |||
d53e400454 | |||
64d1fe3450 | |||
4b091f2eb6 | |||
e70f9b0d0e | |||
d2d10e63c9 | |||
b51f68d780 | |||
c3adfd6d5d | |||
edac56b414 | |||
8c76ca6a53 | |||
0bd2b314e8 | |||
2963be588d | |||
507f2628fd | |||
c227b4da17 | |||
605f145aba | |||
deb7cd5783 | |||
cb0a78cb8f | |||
70e3b6d11f | |||
e041389c68 | |||
38574e37cc | |||
8186dddec1 | |||
47fa00ffef | |||
62f8bb87b7 | |||
5617cb5500 | |||
a4d4188c8d | |||
f487dbae97 | |||
94b98d3f88 | |||
32f101c603 | |||
2feed5ce33 | |||
f7b9114eec | |||
7f017a51bb | |||
efbe1ea8c7 | |||
2b38770ebd | |||
5a96096c5e | |||
ce1519d804 | |||
bd1375f9cb | |||
af641d0912 | |||
6c0ae5e890 | |||
fac0723e92 | |||
68a8a89c49 | |||
8c9432be6a | |||
6e08de6028 | |||
00ed599fe4 | |||
769c7521b5 | |||
e23ccfe913 | |||
7398076be2 | |||
ea98f53dcd | |||
70cbbd614f | |||
95288eba59 | |||
01037308d5 | |||
1765e1b20a | |||
7cb4665bee | |||
d6bddbef2d | |||
fcfec33cf7 | |||
e338ebb720 | |||
741deadff4 | |||
c1cab4aa68 | |||
bf6b04bf89 | |||
a275572e0b | |||
9ec7e23c44 | |||
3c5681c212 | |||
28f1e71e2e | |||
149fd7b65f | |||
a55c230b8a | |||
1f1dc4ade3 | |||
d445ee4c73 | |||
73e52ab55f | |||
3fec225931 | |||
f0443b8859 | |||
b73f692919 | |||
4aeb9dc996 | |||
3b12594054 | |||
0597e93e5e | |||
5f003515a1 | |||
26f8647fea | |||
12f296d800 | |||
75ec632b61 | |||
d6519d4305 | |||
54cf7eaf92 | |||
d0b1e739b1 | |||
b04e2e8877 | |||
fb0d5124f2 | |||
6146a679c9 | |||
84660c44f0 | |||
c398cad059 | |||
35bf6b9790 | |||
e5a59e876e | |||
d464816c37 | |||
007651129a | |||
1968c9b702 | |||
eb54741226 | |||
ce86a518b9 | |||
09a5ea059f | |||
b3cc26bf35 | |||
5f29552be7 | |||
6afa55b7e4 | |||
37d717fe14 | |||
f193cf66d4 | |||
68efa935da | |||
1a0fed5d8e | |||
c3206bd2a0 | |||
26e7fef920 | |||
92e1c8744b | |||
cfe8832dd3 | |||
6a5b048658 | |||
ad0dbde653 | |||
f25c1b4950 | |||
469f752b80 | |||
55f2867db3 | |||
80429002d7 | |||
0910b76be3 | |||
1ccfd6842b | |||
0a0360c8cd | |||
be0201259a | |||
ef2a48329d | |||
81a0c384da | |||
70eaba3cb1 | |||
7e39e78259 | |||
c2122c39ae | |||
fb26ee8a7e | |||
001072721f | |||
65a1ec89ba | |||
ef9fbf258b | |||
a448949f25 | |||
e3232f987a | |||
7ebc3140bb | |||
c42ceef040 | |||
acc2e8afa9 | |||
04bb1bda32 | |||
eed93aaa07 | |||
fc0bb6cdee | |||
10f2ad1556 | |||
6d77b87b13 | |||
2101b46802 | |||
34f6765630 | |||
c58d1acba8 | |||
0ee79f304e | |||
e642de3d6f | |||
eca5a8b695 | |||
79c79f3c70 | |||
a9970d3cb9 | |||
b812f289f5 | |||
215ce0fb57 | |||
5154598845 | |||
1ee80d792c | |||
95284d2f1e | |||
7281f3eb56 | |||
607ef8f6c5 | |||
1ce640cc0b | |||
7bed18fdb1 | |||
c827b50d40 | |||
3c7e3c8e44 | |||
98e38ce4f3 | |||
132cf268c0 | |||
fd7edc9b05 | |||
ecf7c90840 | |||
d78a530af1 | |||
3ebe61db9f | |||
86c2f139c6 | |||
3596c348eb | |||
41a81474e4 | |||
aa2822d137 | |||
55b333d3e3 | |||
00cfad8578 | |||
1891c956e5 | |||
249c050757 | |||
a0081046b6 | |||
635f73b7f1 | |||
74fcd50e2f | |||
b04a2a7be7 | |||
083671e8ac | |||
78ea401e19 | |||
f3ca987bce | |||
2245add9f8 | |||
31004d7fac | |||
3d3f66ed41 | |||
c9c0195da5 | |||
70c0403858 | |||
8d4de82c7f | |||
22c51c2d51 | |||
158bd7c6a0 | |||
4a28d0b583 | |||
0501e6e693 | |||
8450ac09c1 | |||
1af00015e8 | |||
b44c3a3125 | |||
d729f1ca37 | |||
0fc9f00c14 | |||
6c9b339af7 | |||
b7a976af01 | |||
a689037917 | |||
313403c1f1 | |||
60409b8823 | |||
773dc2ec94 | |||
2a98c5d06b | |||
6d1b4ce3c6 | |||
0b2d961b70 | |||
d553b70470 | |||
6954f2cdd7 | |||
8cc832110a | |||
7b8c54b5a1 | |||
e850d175b5 | |||
326f79d59b | |||
ec4954ece2 | |||
b30e782c82 | |||
e34fe5d28e | |||
8581a062f1 | |||
b43971e5e9 | |||
855382170e |
@@ -653,7 +653,8 @@ inline void devirtualize_varray2(const VArray<T1> &varray1,
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* This fallback is used even when one of the inputs could be optimized. It's probably not worth
|
||||
/* This fallback is used even when one of the inputs could be optimized. It's probably not
|
||||
worth
|
||||
* it to optimize just one of the inputs, because then the compiler still has to call into
|
||||
* unknown code, which inhibits many compiler optimizations. */
|
||||
func(varray1, varray2);
|
||||
|
@@ -34,9 +34,11 @@ 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
|
||||
intern/multi_function_procedure_optimization.cc
|
||||
|
||||
FN_cpp_type.hh
|
||||
FN_cpp_type_make.hh
|
||||
@@ -54,9 +56,11 @@ 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
|
||||
FN_multi_function_procedure_optimization.hh
|
||||
FN_multi_function_signature.hh
|
||||
)
|
||||
|
||||
@@ -64,6 +68,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)
|
||||
|
@@ -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
|
||||
|
39
source/blender/functions/FN_multi_function_parallel.hh
Normal file
39
source/blender/functions/FN_multi_function_parallel.hh
Normal file
@@ -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
|
@@ -42,6 +42,55 @@ enum class MFInstructionType {
|
||||
Return,
|
||||
};
|
||||
|
||||
/**
|
||||
* An #MFInstructionCursor points to a position in a multi-function procedure, where an instruction
|
||||
* can be inserted.
|
||||
*/
|
||||
class MFInstructionCursor {
|
||||
public:
|
||||
enum Type {
|
||||
None,
|
||||
Entry,
|
||||
Call,
|
||||
Destruct,
|
||||
Branch,
|
||||
Dummy,
|
||||
};
|
||||
|
||||
private:
|
||||
Type type_ = None;
|
||||
MFInstruction *instruction_ = nullptr;
|
||||
/* Only used when it is a branch instruction. */
|
||||
bool branch_output_ = false;
|
||||
|
||||
public:
|
||||
MFInstructionCursor() = default;
|
||||
MFInstructionCursor(MFCallInstruction &instruction);
|
||||
MFInstructionCursor(MFDestructInstruction &instruction);
|
||||
MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
|
||||
MFInstructionCursor(MFDummyInstruction &instruction);
|
||||
|
||||
static MFInstructionCursor ForEntry();
|
||||
|
||||
MFInstruction *next(MFProcedure &procedure) const;
|
||||
void set_next(MFProcedure &procedure, MFInstruction *new_instruction) const;
|
||||
|
||||
MFInstruction *instruction() const;
|
||||
|
||||
Type type() const;
|
||||
|
||||
friend bool operator==(const MFInstructionCursor &a, const MFInstructionCursor &b)
|
||||
{
|
||||
return a.type_ == b.type_ && a.instruction_ == b.instruction_ &&
|
||||
a.branch_output_ == b.branch_output_;
|
||||
}
|
||||
|
||||
friend bool operator!=(const MFInstructionCursor &a, const MFInstructionCursor &b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A variable is similar to a virtual register in other libraries. During evaluation, every is
|
||||
* either uninitialized or contains a value for every index (remember, a multi-function procedure
|
||||
@@ -73,7 +122,7 @@ class MFVariable : NonCopyable, NonMovable {
|
||||
class MFInstruction : NonCopyable, NonMovable {
|
||||
protected:
|
||||
MFInstructionType type_;
|
||||
Vector<MFInstruction *> prev_;
|
||||
Vector<MFInstructionCursor> prev_;
|
||||
|
||||
friend MFProcedure;
|
||||
friend MFCallInstruction;
|
||||
@@ -89,8 +138,7 @@ class MFInstruction : NonCopyable, NonMovable {
|
||||
* Other instructions that come before this instruction. There can be multiple previous
|
||||
* instructions when branching is used in the procedure.
|
||||
*/
|
||||
Span<MFInstruction *> prev();
|
||||
Span<const MFInstruction *> prev() const;
|
||||
Span<MFInstructionCursor> prev() const;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -246,6 +294,9 @@ class MFProcedure : NonCopyable, NonMovable {
|
||||
Span<MFVariable *> variables();
|
||||
Span<const MFVariable *> variables() const;
|
||||
|
||||
Span<MFDestructInstruction *> destruct_instructions();
|
||||
Span<const MFDestructInstruction *> destruct_instructions() const;
|
||||
|
||||
std::string to_dot() const;
|
||||
|
||||
bool validate() const;
|
||||
@@ -275,6 +326,50 @@ using MFDestructInstruction = fn::MFDestructInstruction;
|
||||
using MFProcedure = fn::MFProcedure;
|
||||
} // namespace multi_function_procedure_types
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFInstructionCursor inline methods.
|
||||
*/
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
|
||||
: type_(Call), instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
|
||||
: type_(Destruct), instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
|
||||
bool branch_output)
|
||||
: type_(Branch), instruction_(&instruction), branch_output_(branch_output)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
|
||||
: type_(Dummy), instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor MFInstructionCursor::ForEntry()
|
||||
{
|
||||
MFInstructionCursor cursor;
|
||||
cursor.type_ = Type::Entry;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
inline MFInstruction *MFInstructionCursor::instruction() const
|
||||
{
|
||||
/* This isn't really const correct unfortunately, because to make it correct we'll need a const
|
||||
* version of #MFInstructionCursor. */
|
||||
return instruction_;
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::Type MFInstructionCursor::type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFVariable inline methods.
|
||||
*/
|
||||
@@ -308,12 +403,7 @@ inline MFInstructionType MFInstruction::type() const
|
||||
return type_;
|
||||
}
|
||||
|
||||
inline Span<MFInstruction *> MFInstruction::prev()
|
||||
{
|
||||
return prev_;
|
||||
}
|
||||
|
||||
inline Span<const MFInstruction *> MFInstruction::prev() const
|
||||
inline Span<MFInstructionCursor> MFInstruction::prev() const
|
||||
{
|
||||
return prev_;
|
||||
}
|
||||
@@ -449,4 +539,14 @@ inline Span<const MFVariable *> MFProcedure::variables() const
|
||||
return variables_;
|
||||
}
|
||||
|
||||
inline Span<MFDestructInstruction *> MFProcedure::destruct_instructions()
|
||||
{
|
||||
return destruct_instructions_;
|
||||
}
|
||||
|
||||
inline Span<const MFDestructInstruction *> MFProcedure::destruct_instructions() const
|
||||
{
|
||||
return destruct_instructions_;
|
||||
}
|
||||
|
||||
} // namespace blender::fn
|
||||
|
@@ -24,31 +24,6 @@
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
/**
|
||||
* An #MFInstructionCursor points to a position in a multi-function procedure, where an instruction
|
||||
* can be inserted.
|
||||
*/
|
||||
class MFInstructionCursor {
|
||||
private:
|
||||
MFInstruction *instruction_ = nullptr;
|
||||
/* Only used when it is a branch instruction. */
|
||||
bool branch_output_ = false;
|
||||
/* Only used when instruction is null. */
|
||||
bool is_entry_ = false;
|
||||
|
||||
public:
|
||||
MFInstructionCursor() = default;
|
||||
|
||||
MFInstructionCursor(MFCallInstruction &instruction);
|
||||
MFInstructionCursor(MFDestructInstruction &instruction);
|
||||
MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
|
||||
MFInstructionCursor(MFDummyInstruction &instruction);
|
||||
|
||||
static MFInstructionCursor Entry();
|
||||
|
||||
void insert(MFProcedure &procedure, MFInstruction *new_instruction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility class to build a #MFProcedure.
|
||||
*/
|
||||
@@ -64,7 +39,7 @@ class MFProcedureBuilder {
|
||||
struct Loop;
|
||||
|
||||
MFProcedureBuilder(MFProcedure &procedure,
|
||||
MFInstructionCursor initial_cursor = MFInstructionCursor::Entry());
|
||||
MFInstructionCursor initial_cursor = MFInstructionCursor::ForEntry());
|
||||
|
||||
MFProcedureBuilder(Span<MFProcedureBuilder *> builders);
|
||||
|
||||
@@ -121,38 +96,6 @@ struct MFProcedureBuilder::Loop {
|
||||
MFDummyInstruction *end = nullptr;
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFInstructionCursor inline methods.
|
||||
*/
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
|
||||
: instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
|
||||
: instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
|
||||
bool branch_output)
|
||||
: instruction_(&instruction), branch_output_(branch_output)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
|
||||
: instruction_(&instruction)
|
||||
{
|
||||
}
|
||||
|
||||
inline MFInstructionCursor MFInstructionCursor::Entry()
|
||||
{
|
||||
MFInstructionCursor cursor;
|
||||
cursor.is_entry_ = true;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* MFProcedureBuilder inline methods.
|
||||
*/
|
||||
@@ -253,7 +196,7 @@ inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable)
|
||||
inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction)
|
||||
{
|
||||
for (MFInstructionCursor &cursor : cursors_) {
|
||||
cursor.insert(*procedure_, instruction);
|
||||
cursor.set_next(*procedure_, instruction);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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_procedure.hh"
|
||||
|
||||
namespace blender::fn::procedure_optimization {
|
||||
|
||||
void move_destructs_up(MFProcedure &procedure);
|
||||
|
||||
}
|
@@ -18,8 +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_multi_function_procedure_optimization.hh"
|
||||
|
||||
#include "FN_field.hh"
|
||||
|
||||
namespace blender::fn {
|
||||
@@ -183,8 +188,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 +253,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 +276,7 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
|
||||
const FieldContext &context,
|
||||
Span<GVMutableArray *> dst_varrays)
|
||||
{
|
||||
|
||||
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()) {
|
||||
@@ -333,8 +339,14 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
|
||||
MFProcedure procedure;
|
||||
build_multi_function_procedure_for_fields(
|
||||
procedure, scope, field_tree_info, varying_fields_to_evaluate);
|
||||
// std::cout << procedure.to_dot() << "\n";
|
||||
// fn::procedure_optimization::move_destructs_up(procedure);
|
||||
// std::cout << procedure.to_dot() << "\n";
|
||||
MFProcedureExecutor procedure_executor{"Procedure", procedure};
|
||||
MFParamsBuilder mf_params{procedure_executor, array_size};
|
||||
fn::ParallelMultiFunction parallel_fn{procedure_executor, 10000};
|
||||
const MultiFunction &fn_to_execute = parallel_fn;
|
||||
|
||||
MFParamsBuilder mf_params{fn_to_execute, array_size};
|
||||
MFContextBuilder mf_context;
|
||||
|
||||
/* Provide inputs to the procedure executor. */
|
||||
@@ -376,7 +388,8 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
|
||||
mf_params.add_uninitialized_single_output(span);
|
||||
}
|
||||
|
||||
procedure_executor.call(mask, mf_params, mf_context);
|
||||
SCOPED_TIMER(__func__);
|
||||
fn_to_execute.call(mask, mf_params, mf_context);
|
||||
}
|
||||
|
||||
/* Evaluate constant fields if necessary. */
|
||||
@@ -419,8 +432,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);
|
||||
|
@@ -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
|
||||
|
109
source/blender/functions/intern/multi_function_parallel.cc
Normal file
109
source/blender/functions/intern/multi_function_parallel.cc
Normal file
@@ -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
|
@@ -21,6 +21,65 @@
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
void MFInstructionCursor::set_next(MFProcedure &procedure, MFInstruction *new_instruction) const
|
||||
{
|
||||
switch (type_) {
|
||||
case Type::None: {
|
||||
break;
|
||||
}
|
||||
case Type::Entry: {
|
||||
procedure.set_entry(*new_instruction);
|
||||
break;
|
||||
}
|
||||
case Type::Call: {
|
||||
static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case Type::Branch: {
|
||||
MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_);
|
||||
if (branch_output_) {
|
||||
branch_instruction.set_branch_true(new_instruction);
|
||||
}
|
||||
else {
|
||||
branch_instruction.set_branch_false(new_instruction);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Destruct: {
|
||||
static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case Type::Dummy: {
|
||||
static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MFInstruction *MFInstructionCursor::next(MFProcedure &procedure) const
|
||||
{
|
||||
switch (type_) {
|
||||
case Type::None:
|
||||
return nullptr;
|
||||
case Type::Entry:
|
||||
return procedure.entry();
|
||||
case Type::Call:
|
||||
return static_cast<MFCallInstruction *>(instruction_)->next();
|
||||
case Type::Branch: {
|
||||
MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_);
|
||||
if (branch_output_) {
|
||||
return branch_instruction.branch_true();
|
||||
}
|
||||
return branch_instruction.branch_false();
|
||||
}
|
||||
case Type::Destruct:
|
||||
return static_cast<MFDestructInstruction *>(instruction_)->next();
|
||||
case Type::Dummy:
|
||||
return static_cast<MFDummyInstruction *>(instruction_)->next();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MFVariable::set_name(std::string name)
|
||||
{
|
||||
name_ = std::move(name);
|
||||
@@ -29,10 +88,10 @@ void MFVariable::set_name(std::string name)
|
||||
void MFCallInstruction::set_next(MFInstruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
instruction->prev_.append(*this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
@@ -71,10 +130,10 @@ void MFBranchInstruction::set_condition(MFVariable *variable)
|
||||
void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
|
||||
{
|
||||
if (branch_true_ != nullptr) {
|
||||
branch_true_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
branch_true_->prev_.remove_first_occurrence_and_reorder({*this, true});
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
instruction->prev_.append({*this, true});
|
||||
}
|
||||
branch_true_ = instruction;
|
||||
}
|
||||
@@ -82,10 +141,10 @@ void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
|
||||
void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
|
||||
{
|
||||
if (branch_false_ != nullptr) {
|
||||
branch_false_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
branch_false_->prev_.remove_first_occurrence_and_reorder({*this, false});
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
instruction->prev_.append({*this, false});
|
||||
}
|
||||
branch_false_ = instruction;
|
||||
}
|
||||
@@ -104,10 +163,10 @@ void MFDestructInstruction::set_variable(MFVariable *variable)
|
||||
void MFDestructInstruction::set_next(MFInstruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
instruction->prev_.append(*this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
@@ -115,10 +174,10 @@ void MFDestructInstruction::set_next(MFInstruction *instruction)
|
||||
void MFDummyInstruction::set_next(MFInstruction *instruction)
|
||||
{
|
||||
if (next_ != nullptr) {
|
||||
next_->prev_.remove_first_occurrence_and_reorder(this);
|
||||
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
||||
}
|
||||
if (instruction != nullptr) {
|
||||
instruction->prev_.append(this);
|
||||
instruction->prev_.append(*this);
|
||||
}
|
||||
next_ = instruction;
|
||||
}
|
||||
@@ -420,7 +479,11 @@ MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction
|
||||
|
||||
Set<const MFInstruction *> checked_instructions;
|
||||
Stack<const MFInstruction *> instructions_to_check;
|
||||
instructions_to_check.push_multiple(target_instruction.prev_);
|
||||
for (const MFInstructionCursor &cursor : target_instruction.prev_) {
|
||||
if (cursor.instruction() != nullptr) {
|
||||
instructions_to_check.push(cursor.instruction());
|
||||
}
|
||||
}
|
||||
|
||||
while (!instructions_to_check.is_empty()) {
|
||||
const MFInstruction &instruction = *instructions_to_check.pop();
|
||||
@@ -467,7 +530,11 @@ MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction
|
||||
if (&instruction == entry_) {
|
||||
check_entry_instruction();
|
||||
}
|
||||
instructions_to_check.push_multiple(instruction.prev_);
|
||||
for (const MFInstructionCursor &cursor : instruction.prev_) {
|
||||
if (cursor.instruction() != nullptr) {
|
||||
instructions_to_check.push(cursor.instruction());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,13 +674,10 @@ class MFProcedureDotExport {
|
||||
|
||||
bool has_to_be_block_begin(const MFInstruction &instruction)
|
||||
{
|
||||
if (procedure_.entry() == &instruction) {
|
||||
return true;
|
||||
}
|
||||
if (instruction.prev().size() != 1) {
|
||||
return true;
|
||||
}
|
||||
if (instruction.prev()[0]->type() == MFInstructionType::Branch) {
|
||||
if (instruction.prev()[0].type() == MFInstructionCursor::Type::Branch) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -623,7 +687,7 @@ class MFProcedureDotExport {
|
||||
{
|
||||
const MFInstruction *current = &representative;
|
||||
while (!this->has_to_be_block_begin(*current)) {
|
||||
current = current->prev()[0];
|
||||
current = current->prev()[0].instruction();
|
||||
if (current == &representative) {
|
||||
/* There is a loop without entry or exit, just break it up here. */
|
||||
break;
|
||||
|
@@ -18,50 +18,6 @@
|
||||
|
||||
namespace blender::fn {
|
||||
|
||||
void MFInstructionCursor::insert(MFProcedure &procedure, MFInstruction *new_instruction)
|
||||
{
|
||||
if (instruction_ == nullptr) {
|
||||
if (is_entry_) {
|
||||
procedure.set_entry(*new_instruction);
|
||||
}
|
||||
else {
|
||||
/* The cursors points at nothing, nothing to do. */
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (instruction_->type()) {
|
||||
case MFInstructionType::Call: {
|
||||
static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Branch: {
|
||||
MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(
|
||||
instruction_);
|
||||
if (branch_output_) {
|
||||
branch_instruction.set_branch_true(new_instruction);
|
||||
}
|
||||
else {
|
||||
branch_instruction.set_branch_false(new_instruction);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Destruct: {
|
||||
static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Dummy: {
|
||||
static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
|
||||
break;
|
||||
}
|
||||
case MFInstructionType::Return: {
|
||||
/* It shouldn't be possible to build a cursor that points to a return instruction. */
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MFProcedureBuilder::add_destruct(MFVariable &variable)
|
||||
{
|
||||
MFDestructInstruction &instruction = procedure_->new_destruct_instruction();
|
||||
|
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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_procedure_optimization.hh"
|
||||
|
||||
namespace blender::fn::procedure_optimization {
|
||||
|
||||
static bool uses_variable(const MFInstruction &instr, const MFVariable &variable)
|
||||
{
|
||||
switch (instr.type()) {
|
||||
case MFInstructionType::Branch:
|
||||
return static_cast<const MFBranchInstruction &>(instr).condition() == &variable;
|
||||
case MFInstructionType::Call:
|
||||
return static_cast<const MFCallInstruction &>(instr).params().contains(&variable);
|
||||
case MFInstructionType::Destruct:
|
||||
return static_cast<const MFDestructInstruction &>(instr).variable() == &variable;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void move_destructs_up(MFProcedure &procedure)
|
||||
{
|
||||
for (MFDestructInstruction *destruct_instr : procedure.destruct_instructions()) {
|
||||
MFVariable *variable = destruct_instr->variable();
|
||||
if (variable == nullptr) {
|
||||
continue;
|
||||
}
|
||||
MFInstruction *last_use_in_block_instr = nullptr;
|
||||
MFInstruction *current_instr = destruct_instr;
|
||||
while (current_instr->prev().size() == 1) {
|
||||
current_instr = current_instr->prev()[0].instruction();
|
||||
if (current_instr == nullptr) {
|
||||
break;
|
||||
}
|
||||
if (uses_variable(*current_instr, *variable)) {
|
||||
last_use_in_block_instr = current_instr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (last_use_in_block_instr == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (last_use_in_block_instr->type() == MFInstructionType::Call) {
|
||||
MFCallInstruction &call_instr = static_cast<MFCallInstruction &>(*last_use_in_block_instr);
|
||||
destruct_instr->prev()[0].set_next(procedure, destruct_instr->next());
|
||||
destruct_instr->set_next(call_instr.next());
|
||||
call_instr.set_next(destruct_instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::fn::procedure_optimization
|
@@ -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
|
||||
|
Reference in New Issue
Block a user