This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/functions/FN_multi_function_procedure.hh
Jacques Lucke d48735cca2 Functions: speedup multi-function procedure executor
This improves performance of the procedure executor on secondary metrics
(i.e. not for the main use case when many elements are processed together,
but for the use case when a single element is processed at a time).

In my benchmark I'm measuring a 50-60% improvement:
* Procedure with a single function (executed many times): `5.8s -> 2.7s`.
* Procedure with 1000 functions (executed many times): `2.4 -> 1.0s`.

The speedup is mainly achieved in multiple ways:
* Store an `Array` of variable states, instead of a map. The array is indexed
  with indices stored in each variable. This also avoids separately allocating
  variable states.
* Move less data around in the scheduler and use a `Stack` instead of `Map`.
  `Map` was used before because it allows for some optimizations that might
  be more important in the future, but they don't matter right now (e.g. joining
  execution paths that diverged earlier).
* Avoid memory allocations by giving the `LinearAllocator` some memory
  from the stack.
2022-06-19 14:25:56 +02:00

553 lines
13 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup fn
*/
#include "FN_multi_function.hh"
namespace blender::fn {
class MFVariable;
class MFInstruction;
class MFCallInstruction;
class MFBranchInstruction;
class MFDestructInstruction;
class MFDummyInstruction;
class MFReturnInstruction;
class MFProcedure;
/** Every instruction has exactly one of these types. */
enum class MFInstructionType {
Call,
Branch,
Destruct,
Dummy,
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
* is always evaluated for many indices at the same time).
*/
class MFVariable : NonCopyable, NonMovable {
private:
MFDataType data_type_;
Vector<MFInstruction *> users_;
std::string name_;
int index_in_graph_;
friend MFProcedure;
friend MFCallInstruction;
friend MFBranchInstruction;
friend MFDestructInstruction;
public:
MFDataType data_type() const;
Span<MFInstruction *> users();
StringRefNull name() const;
void set_name(std::string name);
int index_in_procedure() const;
};
/** Base class for all instruction types. */
class MFInstruction : NonCopyable, NonMovable {
protected:
MFInstructionType type_;
Vector<MFInstructionCursor> prev_;
friend MFProcedure;
friend MFCallInstruction;
friend MFBranchInstruction;
friend MFDestructInstruction;
friend MFDummyInstruction;
friend MFReturnInstruction;
public:
MFInstructionType type() const;
/**
* Other instructions that come before this instruction. There can be multiple previous
* instructions when branching is used in the procedure.
*/
Span<MFInstructionCursor> prev() const;
};
/**
* References a multi-function that is evaluated when the instruction is executed. It also
* references the variables whose data will be passed into the multi-function.
*/
class MFCallInstruction : public MFInstruction {
private:
const MultiFunction *fn_ = nullptr;
MFInstruction *next_ = nullptr;
MutableSpan<MFVariable *> params_;
friend MFProcedure;
public:
const MultiFunction &fn() const;
MFInstruction *next();
const MFInstruction *next() const;
void set_next(MFInstruction *instruction);
void set_param_variable(int param_index, MFVariable *variable);
void set_params(Span<MFVariable *> variables);
Span<MFVariable *> params();
Span<const MFVariable *> params() const;
};
/**
* What makes a branch instruction special is that it has two successor instructions. One that will
* be used when a condition variable was true, and one otherwise.
*/
class MFBranchInstruction : public MFInstruction {
private:
MFVariable *condition_ = nullptr;
MFInstruction *branch_true_ = nullptr;
MFInstruction *branch_false_ = nullptr;
friend MFProcedure;
public:
MFVariable *condition();
const MFVariable *condition() const;
void set_condition(MFVariable *variable);
MFInstruction *branch_true();
const MFInstruction *branch_true() const;
void set_branch_true(MFInstruction *instruction);
MFInstruction *branch_false();
const MFInstruction *branch_false() const;
void set_branch_false(MFInstruction *instruction);
};
/**
* A destruct instruction destructs a single variable. So the variable value will be uninitialized
* after this instruction. All variables that are not output variables of the procedure, have to be
* destructed before the procedure ends. Destructing early is generally a good thing, because it
* might help with memory buffer reuse, which decreases memory-usage and increases performance.
*/
class MFDestructInstruction : public MFInstruction {
private:
MFVariable *variable_ = nullptr;
MFInstruction *next_ = nullptr;
friend MFProcedure;
public:
MFVariable *variable();
const MFVariable *variable() const;
void set_variable(MFVariable *variable);
MFInstruction *next();
const MFInstruction *next() const;
void set_next(MFInstruction *instruction);
};
/**
* This instruction does nothing, it just exists to building a procedure simpler in some cases.
*/
class MFDummyInstruction : public MFInstruction {
private:
MFInstruction *next_ = nullptr;
friend MFProcedure;
public:
MFInstruction *next();
const MFInstruction *next() const;
void set_next(MFInstruction *instruction);
};
/**
* This instruction ends the procedure.
*/
class MFReturnInstruction : public MFInstruction {
};
/**
* Inputs and outputs of the entire procedure network.
*/
struct MFParameter {
MFParamType::InterfaceType type;
MFVariable *variable;
};
struct ConstMFParameter {
MFParamType::InterfaceType type;
const MFVariable *variable;
};
/**
* A multi-function procedure allows composing multi-functions in arbitrary ways. It consists of
* variables and instructions that operate on those variables. Branching and looping within the
* procedure is supported as well.
*
* Typically, a #MFProcedure should be constructed using a #MFProcedureBuilder, which has many more
* utility methods for common use cases.
*/
class MFProcedure : NonCopyable, NonMovable {
private:
LinearAllocator<> allocator_;
Vector<MFCallInstruction *> call_instructions_;
Vector<MFBranchInstruction *> branch_instructions_;
Vector<MFDestructInstruction *> destruct_instructions_;
Vector<MFDummyInstruction *> dummy_instructions_;
Vector<MFReturnInstruction *> return_instructions_;
Vector<MFVariable *> variables_;
Vector<MFParameter> params_;
Vector<destruct_ptr<MultiFunction>> owned_functions_;
MFInstruction *entry_ = nullptr;
friend class MFProcedureDotExport;
public:
MFProcedure() = default;
~MFProcedure();
MFVariable &new_variable(MFDataType data_type, std::string name = "");
MFCallInstruction &new_call_instruction(const MultiFunction &fn);
MFBranchInstruction &new_branch_instruction();
MFDestructInstruction &new_destruct_instruction();
MFDummyInstruction &new_dummy_instruction();
MFReturnInstruction &new_return_instruction();
void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
Span<ConstMFParameter> params() const;
template<typename T, typename... Args> const MultiFunction &construct_function(Args &&...args);
MFInstruction *entry();
const MFInstruction *entry() const;
void set_entry(MFInstruction &entry);
Span<MFVariable *> variables();
Span<const MFVariable *> variables() const;
std::string to_dot() const;
bool validate() const;
private:
bool validate_all_instruction_pointers_set() const;
bool validate_all_params_provided() const;
bool validate_same_variables_in_one_call() const;
bool validate_parameters() const;
bool validate_initialization() const;
struct InitState {
bool can_be_initialized = false;
bool can_be_uninitialized = false;
};
InitState find_initialization_state_before_instruction(const MFInstruction &target_instruction,
const MFVariable &variable) const;
};
namespace multi_function_procedure_types {
using MFVariable = fn::MFVariable;
using MFInstruction = fn::MFInstruction;
using MFCallInstruction = fn::MFCallInstruction;
using MFBranchInstruction = fn::MFBranchInstruction;
using MFDestructInstruction = fn::MFDestructInstruction;
using MFProcedure = fn::MFProcedure;
} // namespace multi_function_procedure_types
/* -------------------------------------------------------------------- */
/** \name #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_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #MFVariable Inline Methods
* \{ */
inline MFDataType MFVariable::data_type() const
{
return data_type_;
}
inline Span<MFInstruction *> MFVariable::users()
{
return users_;
}
inline StringRefNull MFVariable::name() const
{
return name_;
}
inline int MFVariable::index_in_procedure() const
{
return index_in_graph_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #MFInstruction Inline Methods
* \{ */
inline MFInstructionType MFInstruction::type() const
{
return type_;
}
inline Span<MFInstructionCursor> MFInstruction::prev() const
{
return prev_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #MFCallInstruction Inline Methods
* \{ */
inline const MultiFunction &MFCallInstruction::fn() const
{
return *fn_;
}
inline MFInstruction *MFCallInstruction::next()
{
return next_;
}
inline const MFInstruction *MFCallInstruction::next() const
{
return next_;
}
inline Span<MFVariable *> MFCallInstruction::params()
{
return params_;
}
inline Span<const MFVariable *> MFCallInstruction::params() const
{
return params_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #MFBranchInstruction Inline Methods
* \{ */
inline MFVariable *MFBranchInstruction::condition()
{
return condition_;
}
inline const MFVariable *MFBranchInstruction::condition() const
{
return condition_;
}
inline MFInstruction *MFBranchInstruction::branch_true()
{
return branch_true_;
}
inline const MFInstruction *MFBranchInstruction::branch_true() const
{
return branch_true_;
}
inline MFInstruction *MFBranchInstruction::branch_false()
{
return branch_false_;
}
inline const MFInstruction *MFBranchInstruction::branch_false() const
{
return branch_false_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #MFDestructInstruction Inline Methods
* \{ */
inline MFVariable *MFDestructInstruction::variable()
{
return variable_;
}
inline const MFVariable *MFDestructInstruction::variable() const
{
return variable_;
}
inline MFInstruction *MFDestructInstruction::next()
{
return next_;
}
inline const MFInstruction *MFDestructInstruction::next() const
{
return next_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #MFDummyInstruction Inline Methods
* \{ */
inline MFInstruction *MFDummyInstruction::next()
{
return next_;
}
inline const MFInstruction *MFDummyInstruction::next() const
{
return next_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #MFProcedure Inline Methods
* \{ */
inline Span<ConstMFParameter> MFProcedure::params() const
{
static_assert(sizeof(MFParameter) == sizeof(ConstMFParameter));
return params_.as_span().cast<ConstMFParameter>();
}
inline MFInstruction *MFProcedure::entry()
{
return entry_;
}
inline const MFInstruction *MFProcedure::entry() const
{
return entry_;
}
inline Span<MFVariable *> MFProcedure::variables()
{
return variables_;
}
inline Span<const MFVariable *> MFProcedure::variables() const
{
return variables_;
}
template<typename T, typename... Args>
inline const MultiFunction &MFProcedure::construct_function(Args &&...args)
{
destruct_ptr<T> fn = allocator_.construct<T>(std::forward<Args>(args)...);
const MultiFunction &fn_ref = *fn;
owned_functions_.append(std::move(fn));
return fn_ref;
}
/** \} */
} // namespace blender::fn