AK: Make Vector::remove_all_matching O(n) instead of O(n²)

If multiple entries match during a single run of
Vector::remove_all_matching(), the contents of the vector would shift
back multiple times as well. This provides a new approach that iterates
only once over each entry.

Improves the provided benchmark case (running 1000 iterations) from
~900ms to ~8ms on my machine.
This commit is contained in:
Jelle Raaijmakers 2026-01-28 13:57:17 +01:00 committed by Jelle Raaijmakers
parent 4c58be255a
commit 68035fbd98
2 changed files with 27 additions and 8 deletions

View file

@ -590,16 +590,25 @@ public:
template<typename TUnaryPredicate>
bool remove_all_matching(TUnaryPredicate const& predicate)
{
bool something_was_removed = false;
for (size_t i = 0; i < size();) {
if (predicate(at(i))) {
remove(i);
something_was_removed = true;
} else {
++i;
size_t write_index = 0;
for (size_t read_index = 0; read_index < m_size; ++read_index) {
if (predicate(at(read_index))) {
TypedTransfer<StorageType>::delete_(slot(read_index), 1);
continue;
}
if (read_index != write_index) {
TypedTransfer<StorageType>::move(slot(write_index), slot(read_index), 1);
TypedTransfer<StorageType>::delete_(slot(read_index), 1);
}
++write_index;
}
return something_was_removed;
if (write_index == m_size)
return false;
m_size = write_index;
update_metadata();
return true;
}
ALWAYS_INLINE T take_last()

View file

@ -247,6 +247,16 @@ static bool is_inline_element(auto& el, auto& vector)
EXPECT_EQ(ints.size(), 0u); \
} \
\
BENCHMARK_CASE(Vector##_remove_all_matching_trivial) \
{ \
Vector<int> ints; \
for (int i = 0; i < 10000; ++i) { \
ints.append(i); \
} \
ints.remove_all_matching([](int value) { return value % 2 == 0; }); \
EXPECT_EQ(ints.size(), 5000u); \
} \
\
TEST_CASE(Vector##_vector_remove) \
{ \
Vector<int> ints; \