Geometry Nodes: speedup compute context hash generation

Whenever a node group is entered during evaluation, a new compute
context is entered which has a corresponding hash. When node groups
are entered and exited a lot, this can have some overhead. In my test
file with ~100.000 node group invocations, this patch improves performance
by about 7%.

The speedup is achieved in two ways:
* Avoid computing the same hash twice by caching it.
* Invoke the hashing algorithm (md5 currently) only once instead of twice.
This commit is contained in:
Jacques Lucke 2022-12-29 20:45:51 +01:00
parent 5dcce58510
commit a09accb496
3 changed files with 42 additions and 8 deletions

View File

@ -6,6 +6,8 @@
* This file implements some specific compute contexts for concepts in Blender.
*/
#include <optional>
#include "BLI_compute_context.hh"
struct bNode;
@ -41,7 +43,9 @@ class NodeGroupComputeContext : public ComputeContext {
#endif
public:
NodeGroupComputeContext(const ComputeContext *parent, int32_t node_id);
NodeGroupComputeContext(const ComputeContext *parent,
int32_t node_id,
const std::optional<ComputeContextHash> &cached_hash = {});
NodeGroupComputeContext(const ComputeContext *parent, const bNode &node);
int32_t node_id() const

View File

@ -19,11 +19,26 @@ void ModifierComputeContext::print_current_in_line(std::ostream &stream) const
stream << "Modifier: " << modifier_name_;
}
NodeGroupComputeContext::NodeGroupComputeContext(const ComputeContext *parent, const int node_id)
NodeGroupComputeContext::NodeGroupComputeContext(
const ComputeContext *parent,
const int node_id,
const std::optional<ComputeContextHash> &cached_hash)
: ComputeContext(s_static_type, parent), node_id_(node_id)
{
hash_.mix_in(s_static_type, strlen(s_static_type));
hash_.mix_in(&node_id_, sizeof(int32_t));
if (cached_hash.has_value()) {
hash_ = *cached_hash;
}
else {
/* Mix static type and node id into a single buffer so that only a single call to #mix_in is
* necessary. */
const int type_size = strlen(s_static_type);
const int buffer_size = type_size + 1 + sizeof(int32_t);
DynamicStackBuffer<64, 8> buffer_owner(buffer_size, 8);
char *buffer = static_cast<char *>(buffer_owner.buffer());
memcpy(buffer, s_static_type, type_size + 1);
memcpy(buffer + type_size + 1, &node_id_, sizeof(int32_t));
hash_.mix_in(buffer, buffer_size);
}
}
NodeGroupComputeContext::NodeGroupComputeContext(const ComputeContext *parent, const bNode &node)

View File

@ -601,6 +601,12 @@ class LazyFunctionForGroupNode : public LazyFunction {
std::optional<GeometryNodesLazyFunctionSideEffectProvider> lf_side_effect_provider_;
std::optional<lf::GraphExecutor> graph_executor_;
struct Storage {
void *graph_executor_storage = nullptr;
/* To avoid computing the hash more than once. */
std::optional<ComputeContextHash> context_hash_cache;
};
public:
LazyFunctionForGroupNode(const bNode &group_node,
const GeometryNodesLazyFunctionGraphInfo &lf_graph_info,
@ -662,9 +668,13 @@ class LazyFunctionForGroupNode : public LazyFunction {
params.set_default_remaining_outputs();
}
Storage *storage = static_cast<Storage *>(context.storage);
/* The compute context changes when entering a node group. */
bke::NodeGroupComputeContext compute_context{user_data->compute_context,
group_node_.identifier};
bke::NodeGroupComputeContext compute_context{
user_data->compute_context, group_node_.identifier, storage->context_hash_cache};
storage->context_hash_cache = compute_context.hash();
GeoNodesLFUserData group_user_data = *user_data;
group_user_data.compute_context = &compute_context;
if (user_data->modifier_data->socket_log_contexts) {
@ -674,18 +684,23 @@ class LazyFunctionForGroupNode : public LazyFunction {
lf::Context group_context = context;
group_context.user_data = &group_user_data;
group_context.storage = storage->graph_executor_storage;
graph_executor_->execute(params, group_context);
}
void *init_storage(LinearAllocator<> &allocator) const override
{
return graph_executor_->init_storage(allocator);
Storage *s = allocator.construct<Storage>().release();
s->graph_executor_storage = graph_executor_->init_storage(allocator);
return s;
}
void destruct_storage(void *storage) const override
{
graph_executor_->destruct_storage(storage);
Storage *s = static_cast<Storage *>(storage);
graph_executor_->destruct_storage(s->graph_executor_storage);
std::destroy_at(s);
}
};