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_lazy_function_execute.hh
Jacques Lucke 8ba9d7b67a Functions: improve handling of thread-local data in lazy functions
The main goal here is to reduce the number of times thread-local data has
to be looked up using e.g. `EnumerableThreadSpecific.local()`. While this
isn't a bottleneck in many cases, it is when the action performed on the local
data is very short and that happens very often (e.g. logging used sockets
during geometry nodes evaluation).

The solution is to simply pass the thread-local data as parameter to many
functions that use it, instead of looking it up in those functions which
generally is more costly.

The lazy-function graph executor now only looks up the local data if
it knows that it might be on a new thread, otherwise it uses the local data
retrieved earlier.

Alongside with `UserData` there is `LocalUserData` now. This allows users
of the lazy-function evaluation (such as geometry nodes) to have custom
thread-local data that is passed to all the lazy-functions automatically.
This is used for logging now.
2023-05-09 13:13:52 +02:00

130 lines
4.8 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup fn
*
* This file contains common utilities for actually executing a lazy-function.
*/
#include "BLI_parameter_pack_utils.hh"
#include "FN_lazy_function.hh"
namespace blender::fn::lazy_function {
/**
* Most basic implementation of #Params. It does not actually implement any logic for how to
* retrieve inputs or set outputs. Instead, code using #BasicParams has to implement that.
*/
class BasicParams : public Params {
private:
const Span<GMutablePointer> inputs_;
const Span<GMutablePointer> outputs_;
MutableSpan<std::optional<ValueUsage>> input_usages_;
Span<ValueUsage> output_usages_;
MutableSpan<bool> set_outputs_;
public:
BasicParams(const LazyFunction &fn,
const Span<GMutablePointer> inputs,
const Span<GMutablePointer> outputs,
MutableSpan<std::optional<ValueUsage>> input_usages,
Span<ValueUsage> output_usages,
MutableSpan<bool> set_outputs);
void *try_get_input_data_ptr_impl(const int index) const override;
void *try_get_input_data_ptr_or_request_impl(const int index) override;
void *get_output_data_ptr_impl(const int index) override;
void output_set_impl(const int index) override;
bool output_was_set_impl(const int index) const override;
ValueUsage get_output_usage_impl(const int index) const override;
void set_input_unused_impl(const int index) override;
bool try_enable_multi_threading_impl() override;
};
namespace detail {
/**
* Utility to implement #execute_lazy_function_eagerly.
*/
template<typename... Inputs, typename... Outputs, size_t... InIndices, size_t... OutIndices>
inline void execute_lazy_function_eagerly_impl(
const LazyFunction &fn,
UserData *user_data,
LocalUserData *local_user_data,
std::tuple<Inputs...> &inputs,
std::tuple<Outputs *...> &outputs,
std::index_sequence<InIndices...> /* in_indices */,
std::index_sequence<OutIndices...> /* out_indices */)
{
constexpr size_t InputsNum = sizeof...(Inputs);
constexpr size_t OutputsNum = sizeof...(Outputs);
std::array<GMutablePointer, InputsNum> input_pointers;
std::array<GMutablePointer, OutputsNum> output_pointers;
std::array<std::optional<ValueUsage>, InputsNum> input_usages;
std::array<ValueUsage, OutputsNum> output_usages;
std::array<bool, OutputsNum> set_outputs;
(
[&]() {
constexpr size_t I = InIndices;
/* Use `typedef` instead of `using` to work around a compiler bug. */
typedef Inputs T;
const CPPType &type = CPPType::get<T>();
input_pointers[I] = {type, &std::get<I>(inputs)};
}(),
...);
(
[&]() {
constexpr size_t I = OutIndices;
/* Use `typedef` instead of `using` to work around a compiler bug. */
typedef Outputs T;
const CPPType &type = CPPType::get<T>();
output_pointers[I] = {type, std::get<I>(outputs)};
}(),
...);
output_usages.fill(ValueUsage::Used);
set_outputs.fill(false);
LinearAllocator<> allocator;
Context context(fn.init_storage(allocator), user_data, local_user_data);
BasicParams params{
fn, input_pointers, output_pointers, input_usages, output_usages, set_outputs};
fn.execute(params, context);
fn.destruct_storage(context.storage);
/* Make sure all outputs have been computed. */
BLI_assert(!Span<bool>(set_outputs).contains(false));
}
} // namespace detail
/**
* In some cases (mainly for tests), the set of inputs and outputs for a lazy-function is known at
* compile time and one just wants to compute the outputs based on the inputs, without any
* laziness.
*
* This function does exactly that. It takes all inputs in a tuple and writes the outputs to points
* provided in a second tuple. Since all inputs have to be provided, the lazy-function has to
* compute all outputs.
*/
template<typename... Inputs, typename... Outputs>
inline void execute_lazy_function_eagerly(const LazyFunction &fn,
UserData *user_data,
LocalUserData *local_user_data,
std::tuple<Inputs...> inputs,
std::tuple<Outputs *...> outputs)
{
BLI_assert(fn.inputs().size() == sizeof...(Inputs));
BLI_assert(fn.outputs().size() == sizeof...(Outputs));
detail::execute_lazy_function_eagerly_impl(fn,
user_data,
local_user_data,
inputs,
outputs,
std::make_index_sequence<sizeof...(Inputs)>(),
std::make_index_sequence<sizeof...(Outputs)>());
}
} // namespace blender::fn::lazy_function