Commit 4d45e7dc by Jamie Madill Committed by Commit Bot

Add FastVector for simple dynamic vector cases.

This optimized vector keeps an initial fixed size storage but has unbounded growth like a normal std::vector. It operates like a FixedVector initially but then switches the storage to an allocated pool when the element count exceeds the array limit. This gives fast performance in the small case since no dynamic allocation is needed. It also handles the "slow" big case. Bug: angleproject:2763 Change-Id: I9c002b205bd4ac9fc171d1fdc42bc8ea5fe8dabe Reviewed-on: https://chromium-review.googlesource.com/1227794Reviewed-by: 's avatarShahbaz Youssefi <syoussefi@chromium.org> Commit-Queue: Jamie Madill <jmadill@chromium.org>
parent 80efe08d
//
// Copyright 2018 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// FastVector.h:
// A vector class with a initial fixed size and variable growth.
// Based on FixedVector.
//
#ifndef COMMON_FASTVECTOR_H_
#define COMMON_FASTVECTOR_H_
#include "common/debug.h"
#include <algorithm>
#include <array>
#include <initializer_list>
namespace angle
{
template <class T, size_t N, class Storage = std::array<T, N>>
class FastVector final
{
public:
using value_type = typename Storage::value_type;
using size_type = typename Storage::size_type;
using reference = typename Storage::reference;
using const_reference = typename Storage::const_reference;
using pointer = typename Storage::pointer;
using const_pointer = typename Storage::const_pointer;
using iterator = T *;
using const_iterator = const T *;
FastVector();
FastVector(size_type count, const value_type &value);
FastVector(size_type count);
FastVector(const FastVector<T, N, Storage> &other);
FastVector(FastVector<T, N, Storage> &&other);
FastVector(std::initializer_list<value_type> init);
FastVector<T, N, Storage> &operator=(const FastVector<T, N, Storage> &other);
FastVector<T, N, Storage> &operator=(FastVector<T, N, Storage> &&other);
FastVector<T, N, Storage> &operator=(std::initializer_list<value_type> init);
~FastVector();
reference at(size_type pos);
const_reference at(size_type pos) const;
reference operator[](size_type pos);
const_reference operator[](size_type pos) const;
pointer data();
const_pointer data() const;
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
bool empty() const;
size_type size() const;
void clear();
void push_back(const value_type &value);
void push_back(value_type &&value);
void pop_back();
reference front();
const_reference front() const;
reference back();
const_reference back() const;
void swap(FastVector<T, N, Storage> &other);
void resize(size_type count);
void resize(size_type count, const value_type &value);
private:
void assign_from_initializer_list(std::initializer_list<value_type> init);
void ensure_capacity(size_t capacity);
Storage mFixedStorage;
pointer mData = mFixedStorage.data();
size_type mSize = 0;
size_type mReservedSize = N;
};
template <class T, size_t N, class StorageN, size_t M, class StorageM>
bool operator==(const FastVector<T, N, StorageN> &a, const FastVector<T, M, StorageM> &b)
{
return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
}
template <class T, size_t N, class StorageN, size_t M, class StorageM>
bool operator!=(const FastVector<T, N, StorageN> &a, const FastVector<T, M, StorageM> &b)
{
return !(a == b);
}
template <class T, size_t N, class Storage>
FastVector<T, N, Storage>::FastVector()
{
}
template <class T, size_t N, class Storage>
FastVector<T, N, Storage>::FastVector(size_type count, const value_type &value)
{
ensure_capacity(count);
mSize = count;
std::fill(begin(), end(), value);
}
template <class T, size_t N, class Storage>
FastVector<T, N, Storage>::FastVector(size_type count)
{
ensure_capacity(count);
mSize = count;
}
template <class T, size_t N, class Storage>
FastVector<T, N, Storage>::FastVector(const FastVector<T, N, Storage> &other)
{
ensure_capacity(other.mSize);
mSize = other.mSize;
std::copy(other.begin(), other.end(), begin());
}
template <class T, size_t N, class Storage>
FastVector<T, N, Storage>::FastVector(FastVector<T, N, Storage> &&other) : FastVector()
{
swap(other);
}
template <class T, size_t N, class Storage>
FastVector<T, N, Storage>::FastVector(std::initializer_list<value_type> init)
{
assign_from_initializer_list(init);
}
template <class T, size_t N, class Storage>
FastVector<T, N, Storage> &FastVector<T, N, Storage>::operator=(
const FastVector<T, N, Storage> &other)
{
ensure_capacity(other.mSize);
mSize = other.mSize;
std::copy(other.begin(), other.end(), begin());
return *this;
}
template <class T, size_t N, class Storage>
FastVector<T, N, Storage> &FastVector<T, N, Storage>::operator=(FastVector<T, N, Storage> &&other)
{
swap(*this, other);
return *this;
}
template <class T, size_t N, class Storage>
FastVector<T, N, Storage> &FastVector<T, N, Storage>::operator=(
std::initializer_list<value_type> init)
{
assign_from_initializer_list(init);
return *this;
}
template <class T, size_t N, class Storage>
FastVector<T, N, Storage>::~FastVector()
{
clear();
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::reference FastVector<T, N, Storage>::at(size_type pos)
{
ASSERT(pos < mSize);
return mData[pos];
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::const_reference FastVector<T, N, Storage>::at(
size_type pos) const
{
ASSERT(pos < mSize);
return mData[pos];
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::reference FastVector<T, N, Storage>::operator[](size_type pos)
{
ASSERT(pos < mSize);
return mData[pos];
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::const_reference FastVector<T, N, Storage>::operator[](
size_type pos) const
{
ASSERT(pos < mSize);
return mData[pos];
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::const_pointer angle::FastVector<T, N, Storage>::data() const
{
ASSERT(!empty());
return mData;
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::pointer angle::FastVector<T, N, Storage>::data()
{
ASSERT(!empty());
return mData;
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::iterator FastVector<T, N, Storage>::begin()
{
return mData;
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::const_iterator FastVector<T, N, Storage>::begin() const
{
return mData;
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::iterator FastVector<T, N, Storage>::end()
{
return mData + mSize;
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::const_iterator FastVector<T, N, Storage>::end() const
{
return mData + mSize;
}
template <class T, size_t N, class Storage>
bool FastVector<T, N, Storage>::empty() const
{
return mSize == 0;
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::size_type FastVector<T, N, Storage>::size() const
{
return mSize;
}
template <class T, size_t N, class Storage>
void FastVector<T, N, Storage>::clear()
{
resize(0);
}
template <class T, size_t N, class Storage>
ANGLE_INLINE void FastVector<T, N, Storage>::push_back(const value_type &value)
{
if (mSize == mReservedSize)
ensure_capacity(mSize + 1);
mData[mSize++] = value;
}
template <class T, size_t N, class Storage>
void FastVector<T, N, Storage>::push_back(value_type &&value)
{
if (mSize == mReservedSize)
ensure_capacity(mSize + 1);
mData[mSize++] = std::move(value);
}
template <class T, size_t N, class Storage>
void FastVector<T, N, Storage>::pop_back()
{
ASSERT(mSize > 0);
mSize--;
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::reference FastVector<T, N, Storage>::front()
{
ASSERT(mSize > 0);
return mData[0];
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::const_reference FastVector<T, N, Storage>::front() const
{
ASSERT(mSize > 0);
return mData[0];
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::reference FastVector<T, N, Storage>::back()
{
ASSERT(mSize > 0);
return mData[mSize - 1];
}
template <class T, size_t N, class Storage>
typename FastVector<T, N, Storage>::const_reference FastVector<T, N, Storage>::back() const
{
ASSERT(mSize > 0);
return mData[mSize - 1];
}
template <class T, size_t N, class Storage>
void FastVector<T, N, Storage>::swap(FastVector<T, N, Storage> &other)
{
std::swap(mSize, other.mSize);
pointer tempData = other.mData;
if (mData == mFixedStorage.data())
other.mData = other.mFixedStorage.data();
else
other.mData = mData;
if (tempData == other.mFixedStorage.data())
mData = mFixedStorage.data();
else
mData = tempData;
std::swap(mReservedSize, other.mReservedSize);
std::swap(mFixedStorage, other.mFixedStorage);
}
template <class T, size_t N, class Storage>
void FastVector<T, N, Storage>::resize(size_type count)
{
if (count < mSize)
{
if (count == 0 && mData && mData != mFixedStorage.data())
{
delete[] mData;
mData = mFixedStorage.data();
mReservedSize = N;
}
mSize = count;
}
else if (count > mSize)
{
ensure_capacity(count);
mSize = count;
}
}
template <class T, size_t N, class Storage>
void FastVector<T, N, Storage>::resize(size_type count, const value_type &value)
{
if (count < mSize)
{
if (count == 0 && mData && mData != mFixedStorage.data())
{
delete[] mData;
mData = mFixedStorage.data();
mReservedSize = N;
}
mSize = count;
}
else if (count > mSize)
{
ensure_capacity(count);
mSize = count;
std::fill(mData, mData + mSize, value);
}
}
template <class T, size_t N, class Storage>
void FastVector<T, N, Storage>::assign_from_initializer_list(std::initializer_list<value_type> init)
{
ensure_capacity(init.size());
mSize = init.size();
size_t index = 0;
for (auto &value : init)
{
mData[index++] = value;
}
}
template <class T, size_t N, class Storage>
void FastVector<T, N, Storage>::ensure_capacity(size_t capacity)
{
// We have a minimum capacity of N.
if (mReservedSize < capacity)
{
ASSERT(capacity > N);
size_type newSize = std::max(mReservedSize, N);
while (newSize < capacity)
{
newSize *= 2;
}
pointer newData = new value_type[newSize];
if (mSize > 0)
{
std::move(begin(), end(), newData);
}
if (mData && mData != mFixedStorage.data())
{
delete[] mData;
}
mData = newData;
mReservedSize = newSize;
}
}
} // namespace angle
#endif // COMMON_FASTVECTOR_H_
//
// Copyright 2018 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// FixedVector_unittest:
// Tests of the FastVector class
//
#include <gtest/gtest.h>
#include "common/FastVector.h"
namespace angle
{
// Make sure the various constructors compile and do basic checks
TEST(FastVector, Constructors)
{
FastVector<int, 5> defaultContructor;
EXPECT_EQ(0u, defaultContructor.size());
FastVector<int, 5> count(3);
EXPECT_EQ(3u, count.size());
FastVector<int, 5> countAndValue(3, 2);
EXPECT_EQ(3u, countAndValue.size());
EXPECT_EQ(2, countAndValue[1]);
FastVector<int, 5> copy(countAndValue);
EXPECT_EQ(copy, countAndValue);
FastVector<int, 5> copyRValue(std::move(count));
EXPECT_EQ(3u, copyRValue.size());
FastVector<int, 5> initializerList{1, 2, 3, 4, 5};
EXPECT_EQ(5u, initializerList.size());
EXPECT_EQ(3, initializerList[2]);
FastVector<int, 5> assignCopy(copyRValue);
EXPECT_EQ(3u, assignCopy.size());
FastVector<int, 5> assignRValue(std::move(assignCopy));
EXPECT_EQ(3u, assignRValue.size());
FastVector<int, 5> assignmentInitializerList = {1, 2, 3, 4, 5};
EXPECT_EQ(5u, assignmentInitializerList.size());
EXPECT_EQ(3, assignmentInitializerList[2]);
}
// Test indexing operations (at, operator[])
TEST(FastVector, Indexing)
{
FastVector<int, 5> vec = {0, 1, 2, 3, 4};
for (int i = 0; i < 5; ++i)
{
EXPECT_EQ(i, vec.at(i));
EXPECT_EQ(vec[i], vec.at(i));
}
}
// Test the push_back functions
TEST(FastVector, PushBack)
{
FastVector<int, 5> vec;
vec.push_back(1);
EXPECT_EQ(1, vec[0]);
vec.push_back(1);
vec.push_back(1);
vec.push_back(1);
vec.push_back(1);
EXPECT_EQ(5u, vec.size());
}
// Tests growing the fast vector beyond the fixed storage.
TEST(FastVector, Growth)
{
constexpr size_t kSize = 4;
FastVector<size_t, kSize> vec;
for (size_t i = 0; i < kSize * 2; ++i)
{
vec.push_back(i);
}
EXPECT_EQ(kSize * 2, vec.size());
for (size_t i = kSize * 2; i > 0; --i)
{
ASSERT_EQ(vec.back(), i - 1);
vec.pop_back();
}
EXPECT_EQ(0u, vec.size());
}
// Test the pop_back function
TEST(FastVector, PopBack)
{
FastVector<int, 5> vec;
vec.push_back(1);
EXPECT_EQ(1, (int)vec.size());
vec.pop_back();
EXPECT_EQ(0, (int)vec.size());
}
// Test the back function
TEST(FastVector, Back)
{
FastVector<int, 5> vec;
vec.push_back(1);
vec.push_back(2);
EXPECT_EQ(2, vec.back());
}
// Test the back function
TEST(FastVector, Front)
{
FastVector<int, 5> vec;
vec.push_back(1);
vec.push_back(2);
EXPECT_EQ(1, vec.front());
}
// Test the sizing operations
TEST(FastVector, Size)
{
FastVector<int, 5> vec;
EXPECT_TRUE(vec.empty());
EXPECT_EQ(0u, vec.size());
vec.push_back(1);
EXPECT_FALSE(vec.empty());
EXPECT_EQ(1u, vec.size());
}
// Test clearing the vector
TEST(FastVector, Clear)
{
FastVector<int, 5> vec = {0, 1, 2, 3, 4};
vec.clear();
EXPECT_TRUE(vec.empty());
}
// Test clearing the vector larger than the fixed size.
TEST(FastVector, ClearWithLargerThanFixedSize)
{
FastVector<int, 3> vec = {0, 1, 2, 3, 4};
vec.clear();
EXPECT_TRUE(vec.empty());
}
// Test resizing the vector
TEST(FastVector, Resize)
{
FastVector<int, 5> vec;
vec.resize(5u, 1);
EXPECT_EQ(5u, vec.size());
EXPECT_EQ(1, vec[4]);
vec.resize(2u);
EXPECT_EQ(2u, vec.size());
// Resize to larger than minimum
vec.resize(10u, 2);
EXPECT_EQ(10u, vec.size());
EXPECT_EQ(2, vec[8]);
// Resize back to smaller
vec.resize(2u);
EXPECT_EQ(2u, vec.size());
}
// Test iterating over the vector
TEST(FastVector, Iteration)
{
FastVector<int, 5> vec = {0, 1, 2, 3};
int vistedCount = 0;
for (int value : vec)
{
EXPECT_EQ(vistedCount, value);
vistedCount++;
}
EXPECT_EQ(4, vistedCount);
}
// Tests that equality comparisons work even if reserved size differs.
TEST(FastVector, EqualityWithDifferentReservedSizes)
{
FastVector<int, 3> vec1 = {1, 2, 3, 4, 5};
FastVector<int, 5> vec2 = {1, 2, 3, 4, 5};
EXPECT_EQ(vec1, vec2);
vec2.push_back(6);
EXPECT_NE(vec1, vec2);
}
// Tests vector operations with a non copyable type.
TEST(FastVector, NonCopyable)
{
struct s : angle::NonCopyable
{
s() : x(0) {}
s(int xin) : x(xin) {}
s(s &&other) : x(other.x) {}
s &operator=(s &&other)
{
x = other.x;
return *this;
}
int x;
};
FastVector<s, 3> vec;
vec.push_back(3);
EXPECT_EQ(3, vec[0].x);
FastVector<s, 3> copy = std::move(vec);
EXPECT_EQ(1u, copy.size());
EXPECT_EQ(3, copy[0].x);
}
} // namespace angle
......@@ -5,6 +5,7 @@
libangle_common_sources = [
"src/common/Color.h",
"src/common/Color.inl",
"src/common/FastVector.h",
"src/common/FixedVector.h",
"src/common/Float16ToFloat32.cpp",
"src/common/MemoryBuffer.cpp",
......
......@@ -3,6 +3,7 @@
# found in the LICENSE file.
angle_unittests_sources = [
"../common/FastVector_unittest.cpp",
"../common/FixedVector_unittest.cpp",
"../common/Optional_unittest.cpp",
"../common/aligned_memory_unittest.cpp",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment