BLI: refactor IndexMask for better performance and memory usage #104629

Merged
Jacques Lucke merged 254 commits from JacquesLucke/blender:index-mask-refactor into main 2023-05-24 18:11:47 +02:00
3 changed files with 38 additions and 15 deletions
Showing only changes of commit c693138f83 - Show all commits

View File

@ -73,8 +73,8 @@ class IndexMask {
int64_t first() const;
int64_t last() const;
int64_t min_array_size() const;
std::optional<RawMaskIterator> find(const int64_t index) const;
bool contains(const int64_t index) const;
std::optional<RawMaskIterator> find(const int64_t query_index) const;
bool contains(const int64_t query_index) const;
RawMaskIterator index_to_iterator(const int64_t index) const;
int64_t iterator_to_index(const RawMaskIterator &it) const;
Review

Hmm, why would the first element not be 0 always?

Hmm, why would the first element not be 0 always?
Review

The first element is often not 0 when the IndexMask is a slice of another mask.

The first element is often not 0 when the `IndexMask` is a slice of another mask.

View File

@ -374,32 +374,42 @@ IndexMask IndexMask::from_predicate_impl(
return mask;
}
std::optional<RawMaskIterator> IndexMask::find(const int64_t index) const
std::optional<RawMaskIterator> IndexMask::find(const int64_t query_index) const
{
if (this->is_empty()) {
return std::nullopt;
}
if (index < this->first()) {
if (query_index < this->first()) {
return std::nullopt;
}
if (index > this->last()) {
if (query_index > this->last()) {
return std::nullopt;
}
for (const int64_t segment_i : IndexRange(data_.segments_num)) {
const OffsetSpan<int64_t, int16_t> segment = this->segment(segment_i);
for (const int64_t index_in_segment : segment.index_range()) {
if (segment[index_in_segment] == index) {
return RawMaskIterator{segment_i, int16_t(index_in_segment)};
}
}
const int64_t segment_i = std::upper_bound(data_.segment_offsets,
data_.segment_offsets + data_.segments_num,
query_index) -
data_.segment_offsets - 1;
const OffsetSpan<int64_t, int16_t> segment = this->segment(segment_i);
const Span<int16_t> local_segment = segment.base_span();
const int64_t local_query_index = query_index - segment.offset();
if (local_query_index > local_segment.last()) {
return std::nullopt;
}
return std::nullopt;
const int64_t index_in_segment = std::lower_bound(local_segment.begin(),
local_segment.end(),
local_query_index) -
local_segment.begin();
BLI_assert(index_in_segment < local_segment.size());
if (local_segment[index_in_segment] != local_query_index) {
return std::nullopt;
}
return RawMaskIterator{segment_i, int16_t(index_in_segment)};
}
bool IndexMask::contains(const int64_t index) const
bool IndexMask::contains(const int64_t query_index) const
{
return this->find(index).has_value();
return this->find(query_index).has_value();
}
template IndexMask IndexMask::from_indices(Span<int32_t>, IndexMaskMemory &);

View File

@ -255,6 +255,19 @@ TEST(index_mask, IndexIteratorConversionFuzzy)
const int64_t new_index = sub_mask.iterator_to_index(it);
EXPECT_EQ(index, new_index);
}
for ([[maybe_unused]] const int64_t _ : IndexRange(100)) {
const int64_t index = rng.get_int32(int(indices.size() - 1000));
for (const int64_t offset : {0, 1, 2, 100, 500}) {
const int64_t index_to_search = indices[index] + offset;
const bool contained = std::binary_search(indices.begin(), indices.end(), index_to_search);
const std::optional<RawMaskIterator> it = mask.find(index_to_search);
EXPECT_EQ(contained, it.has_value());
if (contained) {
EXPECT_EQ(index_to_search, mask[*it]);
}
}
}
}
TEST(index_mask, FromPredicateFuzzy)