From 6a05e5161bc7092cf3b25ea9f0164c5673f57bfb Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 16 Jun 2023 14:21:46 +0200 Subject: [PATCH] Fix: Quadratic performance of simulation state frame lookup Searching for a simulation state at a particular frame was implemented with a linear loop. The timeline did that for every visible frame, giving quadratic performance overall when zoomed out. Since the states are already assumed to be sorted by frame, we can use binary search instead giving logarithmic performance for each lookup instead. In the test file from #108097, instead of dropping to 20-30 FPS after about 4000 frames, I observed the original 70 FPS. Pull Request: https://projects.blender.org/blender/blender/pulls/109037 --- .../blenkernel/BKE_simulation_state.hh | 3 + .../blenkernel/intern/simulation_state.cc | 84 ++++++++++++------- 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/source/blender/blenkernel/BKE_simulation_state.hh b/source/blender/blenkernel/BKE_simulation_state.hh index 41e698af809..e8630999ceb 100644 --- a/source/blender/blenkernel/BKE_simulation_state.hh +++ b/source/blender/blenkernel/BKE_simulation_state.hh @@ -149,6 +149,9 @@ struct StatesAroundFrame { class ModifierSimulationCache { private: mutable std::mutex states_at_frames_mutex_; + /** + * All simulation states, sorted by frame. + */ Vector> states_at_frames_; /** * Used for baking to deduplicate arrays when writing and writing from storage. Sharing info diff --git a/source/blender/blenkernel/intern/simulation_state.cc b/source/blender/blenkernel/intern/simulation_state.cc index ef912a89ea7..f93cbe0105b 100644 --- a/source/blender/blenkernel/intern/simulation_state.cc +++ b/source/blender/blenkernel/intern/simulation_state.cc @@ -9,6 +9,7 @@ #include "DNA_node_types.h" #include "DNA_pointcloud_types.h" +#include "BLI_binary_search.hh" #include "BLI_fileops.hh" #include "BLI_hash_md5.h" #include "BLI_path_util.h" @@ -92,15 +93,34 @@ void ModifierSimulationCache::try_discover_bake(const StringRefNull absolute_bak } } +static int64_t find_state_at_frame( + const Span> states, const SubFrame &frame) +{ + const int64_t i = binary_search::find_predicate_begin( + states, [&](const auto &item) { return item->frame >= frame; }); + if (i == states.size()) { + return -1; + } + return i; +} + +static int64_t find_state_at_frame_exact( + const Span> states, const SubFrame &frame) +{ + const int64_t i = find_state_at_frame(states, frame); + if (i == -1) { + return -1; + } + if (states[i]->frame != frame) { + return -1; + } + return i; +} + bool ModifierSimulationCache::has_state_at_frame(const SubFrame &frame) const { std::lock_guard lock(states_at_frames_mutex_); - for (const auto &item : states_at_frames_) { - if (item->frame == frame) { - return true; - } - } - return false; + return find_state_at_frame_exact(states_at_frames_, frame) != -1; } bool ModifierSimulationCache::has_states() const @@ -113,23 +133,26 @@ const ModifierSimulationState *ModifierSimulationCache::get_state_at_exact_frame const SubFrame &frame) const { std::lock_guard lock(states_at_frames_mutex_); - for (const auto &item : states_at_frames_) { - if (item->frame == frame) { - return &item->state; - } + const int64_t i = find_state_at_frame_exact(states_at_frames_, frame); + if (i == -1) { + return nullptr; } - return nullptr; + return &states_at_frames_[i]->state; } ModifierSimulationState &ModifierSimulationCache::get_state_at_frame_for_write( const SubFrame &frame) { std::lock_guard lock(states_at_frames_mutex_); - for (const auto &item : states_at_frames_) { - if (item->frame == frame) { - return item->state; - } + const int64_t i = find_state_at_frame_exact(states_at_frames_, frame); + if (i != -1) { + return states_at_frames_[i]->state; } + + if (!states_at_frames_.is_empty()) { + BLI_assert(frame > states_at_frames_.last()->frame); + } + states_at_frames_.append(std::make_unique()); states_at_frames_.last()->frame = frame; states_at_frames_.last()->state.owner_ = this; @@ -139,23 +162,22 @@ ModifierSimulationState &ModifierSimulationCache::get_state_at_frame_for_write( StatesAroundFrame ModifierSimulationCache::get_states_around_frame(const SubFrame &frame) const { std::lock_guard lock(states_at_frames_mutex_); - StatesAroundFrame states_around_frame; - for (const auto &item : states_at_frames_) { - if (item->frame < frame) { - if (states_around_frame.prev == nullptr || item->frame > states_around_frame.prev->frame) { - states_around_frame.prev = item.get(); - } - } - if (item->frame == frame) { - if (states_around_frame.current == nullptr) { - states_around_frame.current = item.get(); - } - } - if (item->frame > frame) { - if (states_around_frame.next == nullptr || item->frame < states_around_frame.next->frame) { - states_around_frame.next = item.get(); - } + const int64_t i = find_state_at_frame(states_at_frames_, frame); + StatesAroundFrame states_around_frame{}; + if (i == -1) { + if (!states_at_frames_.is_empty() && states_at_frames_.last()->frame < frame) { + states_around_frame.prev = states_at_frames_.last().get(); } + return states_around_frame; + } + if (states_at_frames_[i]->frame == frame) { + states_around_frame.current = states_at_frames_[i].get(); + } + if (i > 0) { + states_around_frame.prev = states_at_frames_[i - 1].get(); + } + if (i < states_at_frames_.size() - 2) { + states_around_frame.next = states_at_frames_[i + 1].get(); } return states_around_frame; }