虽然从std::deque 中删除元素的最快方法确实是erase 方法,但std::deque 似乎不是适合您情况的理想数据结构。由于您的元素已排序(它们不断增加),您可以在O(log(n)) 时间内找到需要擦除的索引。不幸的是,std::deque::erase 的复杂度为 O(n),因此快速搜索的收益完全被削弱了。我们可以做得更好。
由于您存储在数据结构中的元素不需要被破坏(因为它们只是整数),因此可以创建一个在恒定时间内擦除这些元素的数据结构。例如,这里有一个环形缓冲区实现:
template <typename T>
class RingBuffer
{
public:
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable!");
static_assert(std::is_trivially_destructible_v<T>, "T must be trivially destructable!");
RingBuffer() :
m_Items(nullptr),
m_Capacity(0),
m_StartIndex(0),
m_EndIndex(0),
m_Version(0)
{
}
RingBuffer(const RingBuffer&) = delete;
RingBuffer& operator=(const RingBuffer&) = delete;
~RingBuffer()
{
delete[] m_Items;
}
void Add(T item)
{
m_Version++;
if (m_EndIndex + 1 == m_StartIndex || (m_EndIndex >= m_StartIndex && m_EndIndex >= m_Capacity && m_StartIndex == 0))
Grow();
size_t elementIndex;
if (m_EndIndex >= m_Capacity)
{
elementIndex = 0;
m_EndIndex = 1;
}
else
{
elementIndex = m_EndIndex++;
}
m_Items[elementIndex] = std::move(item);
}
void EraseFirstN(size_t eraseCount)
{
m_Version++;
auto count = size();
assert(eraseCount <= count);
if (eraseCount == count)
{
m_StartIndex = m_EndIndex = 0;
return;
}
m_StartIndex += eraseCount;
if (m_StartIndex >= m_Capacity)
m_StartIndex -= m_Capacity;
}
size_t size() const
{
if (m_EndIndex >= m_StartIndex)
return m_EndIndex - m_StartIndex;
return m_Capacity - m_StartIndex + m_EndIndex;
}
struct Iterator : std::iterator<std::random_access_iterator_tag, T>
{
Iterator() :
m_Container(nullptr),
m_Index(0),
m_Version(0)
{
}
Iterator(RingBuffer<T>* container, size_t index, size_t version) :
m_Container(container),
m_Index(index),
m_Version(version)
{
}
Iterator& operator+=(ptrdiff_t value) { m_Index += value; return *this; }
Iterator& operator-=(ptrdiff_t value) { m_Index -= value; return *this; }
T& operator*() { assert(m_Version == m_Container->m_Version); return (*m_Container)[m_Index]; }
const T& operator*() const { assert(m_Version == m_Container->m_Version); return (*m_Container)[m_Index]; }
T* operator->() { assert(m_Version == m_Container->m_Version); return &(*m_Container)[m_Index]; }
const T* operator->() const { assert(m_Version == m_Container->m_Version); return &(*m_Container)[m_Index]; }
T& operator[](const int& index) { assert(m_Version == m_Container->m_Version); return (*m_Container)[m_Index + index]; }
const T& operator[](const int& index) const { assert(m_Version == m_Container->m_Version); return (*m_Container)[m_Index + index]; }
Iterator& operator++() { m_Index++; return *this; }
Iterator& operator--() { m_Index--; return *this; }
Iterator operator++(int) { return Iterator(m_Container, m_Index++, m_Version); }
Iterator operator--(int) { return Iterator(m_Container, m_Index--, m_Version); }
ptrdiff_t operator-(const Iterator& other) const { assert(m_Container == other.m_Container && m_Version == other.m_Version); return static_cast<ptrdiff_t>(m_Index - other.m_Index); }
Iterator operator+(const ptrdiff_t& index) const { return Iterator(m_Container, m_Index + index, m_Version); }
Iterator operator-(const ptrdiff_t& index) const { return Iterator(m_Container, m_Index - index, m_Version); }
friend Iterator operator+(const ptrdiff_t& index, const Iterator& other) { return other + index; }
friend Iterator operator-(const ptrdiff_t& index, const Iterator& other) { return Iterator(other.m_Container, index - other.m_Index, other.m_Version); }
bool operator==(const Iterator& other) const { assert(m_Container == other.m_Container && m_Version == other.m_Version); return m_Index == other,m_Index; }
bool operator!=(const Iterator& other) const { assert(m_Container == other.m_Container && m_Version == other.m_Version); return m_Index != other.m_Index; }
bool operator>(const Iterator& other) const { assert(m_Container == other.m_Container && m_Version == other.m_Version); return m_Index > other.m_Index; }
bool operator<(const Iterator& other) const { assert(m_Container == other.m_Container && m_Version == other.m_Version); return m_Index < other.m_Index; }
bool operator>=(const Iterator& other) const { assert(m_Container == other.m_Container && m_Version == other.m_Version); return m_Index >= other.m_Index; }
bool operator<=(const Iterator& other) const { assert(m_Container == other.m_Container && m_Version == other.m_Version); return m_Index <= other.m_Index; }
private:
RingBuffer<T>* m_Container;
size_t m_Index;
size_t m_Version;
};
Iterator begin()
{
return Iterator(this, 0, m_Version);
}
Iterator end()
{
return Iterator(this, size(), m_Version);
}
T& operator[](size_t index)
{
assert(index < size());
index += m_StartIndex;
if (index >= m_Capacity)
index -= m_Capacity;
return m_Items[index];
}
private:
void Grow()
{
auto newCapacity = std::max(m_Capacity * 2, m_Capacity + 4);
auto newItems = new T[newCapacity];
if (m_EndIndex >= m_StartIndex)
{
memcpy(newItems, m_Items + m_StartIndex, (m_EndIndex - m_StartIndex) * sizeof(T));
m_EndIndex = m_EndIndex - m_StartIndex;
}
else
{
memcpy(newItems, m_Items + m_StartIndex, (m_Capacity - m_StartIndex) * sizeof(T));
memcpy(newItems + m_Capacity - m_StartIndex, m_Items, m_EndIndex * sizeof(T));
m_EndIndex = m_Capacity - m_StartIndex + m_EndIndex;
}
delete[] m_Items;
m_StartIndex = 0;
m_Items = newItems;
m_Capacity = newCapacity;
}
private:
T* m_Items;
size_t m_Capacity;
size_t m_StartIndex;
size_t m_EndIndex;
size_t m_Version;
};
从这个数据结构中擦除比使用std::deque 快得多。这是我使用的基准测试代码:
constexpr size_t kCount = 100 * 1000 * 1000;
size_t everIncreasingIndex = 0;
size_t elementToEraseUpTo = 5674112;
std::deque<size_t> deque;
RingBuffer<size_t> ringBuffer;
std::cout.setf(std::ios::fixed);
std::cout.precision(5);
for (size_t u = 0; u < 10; u++)
{
for (size_t i = 0; i < kCount; i++)
{
deque.push_back(everIncreasingIndex);
ringBuffer.Add(everIncreasingIndex);
everIncreasingIndex++;
}
{
Stopwatch stopwatch;
auto it = std::lower_bound(deque.begin(), deque.end(), elementToEraseUpTo);
deque.erase(deque.begin(), it);
auto elapsed = stopwatch.ElapsedSeconds();
std::cout << "std::deque took " << elapsed * 1000 << " ms." << std::endl;
}
{
Stopwatch stopwatch;
auto it = std::lower_bound(ringBuffer.begin(), ringBuffer.end(), elementToEraseUpTo);
ringBuffer.EraseFirstN(it - ringBuffer.begin());
auto elapsed = stopwatch.ElapsedSeconds();
std::cout << "Ring buffer took " << elapsed * 1000 << " ms." << std::endl;
}
elementToEraseUpTo *= 2;
}
在我的机器上,结果如下:
std::deque took 6.43080 ms.
Ring buffer took 0.00220 ms.
std::deque took 6.40030 ms.
Ring buffer took 0.00170 ms.
std::deque took 12.83000 ms.
Ring buffer took 0.00160 ms.
std::deque took 25.61430 ms.
Ring buffer took 0.00240 ms.
std::deque took 51.16480 ms.
Ring buffer took 0.00280 ms.
std::deque took 102.56710 ms.
Ring buffer took 0.00270 ms.
std::deque took 205.54640 ms.
Ring buffer took 0.00250 ms.
std::deque took 411.39580 ms.
Ring buffer took 0.00250 ms.
std::deque took 170.58990 ms.
Ring buffer took 0.00300 ms.
std::deque took 98.51280 ms.
Ring buffer took 0.00230 ms.