-
Notifications
You must be signed in to change notification settings - Fork 36
Simple and flat ArrayTable
#18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,8 +47,8 @@ public: | |
uint32_t count); | ||
// Grow as necessary and return pointer for id. | ||
TYPE *ensureId(ObjectId id); | ||
TYPE *pointer(ObjectId id) const; | ||
TYPE &ref(ObjectId id) const; | ||
TYPE *pointer(ObjectId id); | ||
TYPE &ref(ObjectId id); | ||
size_t size() const { return size_; } | ||
void clear(); | ||
|
||
|
@@ -58,19 +58,16 @@ public: | |
|
||
private: | ||
ArrayBlock<TYPE> *makeBlock(uint32_t size); | ||
void pushBlock(ArrayBlock<TYPE> *block); | ||
void pushBlock(size_t size); | ||
void deleteBlocks(); | ||
|
||
size_t size_; | ||
// Block index of free block (blocks_[size - 1]). | ||
BlockIdx free_block_idx_; | ||
// Index of next free object in free_block_idx_. | ||
ObjectIdx free_idx_; | ||
// Don't use std::vector so growing blocks_ can be thread safe. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess you missed the comment that you deleted that explains why std::vector cannot be used here. If you look at the commit history you will see that it used to use std::vector. When the the vector grows it fails with multiple threads. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't miss it. However, I suppose implementations other than libstdc++ might exhibit different behavior on lower optimization levels. So I pushed a manual swap implementation just to be sure. |
||
size_t blocks_size_; | ||
size_t blocks_capacity_; | ||
ArrayBlock<TYPE>* *blocks_; | ||
ArrayBlock<TYPE>* *prev_blocks_; | ||
std::vector<ArrayBlock<TYPE>> blocks_; | ||
std::vector<ArrayBlock<TYPE>> prev_blocks_; | ||
// Linked list of free arrays indexed by array size. | ||
std::vector<ObjectId> free_list_; | ||
static constexpr ObjectId idx_mask_ = block_size - 1; | ||
|
@@ -80,28 +77,24 @@ template <class TYPE> | |
ArrayTable<TYPE>::ArrayTable() : | ||
size_(0), | ||
free_block_idx_(block_idx_null), | ||
free_idx_(object_idx_null), | ||
blocks_size_(0), | ||
blocks_capacity_(1024), | ||
blocks_(new ArrayBlock<TYPE>*[blocks_capacity_]), | ||
prev_blocks_(nullptr) | ||
free_idx_(object_idx_null) | ||
{ | ||
blocks_.reserve(1024); | ||
} | ||
|
||
template <class TYPE> | ||
ArrayTable<TYPE>::~ArrayTable() | ||
{ | ||
deleteBlocks(); | ||
delete [] blocks_; | ||
delete [] prev_blocks_; | ||
} | ||
|
||
template <class TYPE> | ||
void | ||
ArrayTable<TYPE>::deleteBlocks() | ||
{ | ||
for (size_t i = 0; i < blocks_size_; i++) | ||
delete blocks_[i]; | ||
for (size_t i = 0; i < blocks_.size(); i++) | ||
blocks_[i].free(); | ||
blocks_.clear(); | ||
} | ||
|
||
template <class TYPE> | ||
|
@@ -120,12 +113,12 @@ ArrayTable<TYPE>::make(uint32_t count, | |
free_list_[count] = *head; | ||
} | ||
else { | ||
ArrayBlock<TYPE> *block = blocks_size_ ? blocks_[free_block_idx_] : nullptr; | ||
ArrayBlock<TYPE> *block = blocks_.size() ? &blocks_[free_block_idx_] : nullptr; | ||
if ((free_idx_ == object_idx_null | ||
&& free_block_idx_ == block_idx_null) | ||
|| free_idx_ + count >= block->size()) { | ||
uint32_t size = block_size; | ||
if (blocks_size_ == 0 | ||
if (blocks_.size() == 0 | ||
// First block starts at idx 1. | ||
&& count > block_size - 1) | ||
size = count + 1; | ||
|
@@ -145,32 +138,36 @@ template <class TYPE> | |
ArrayBlock<TYPE> * | ||
ArrayTable<TYPE>::makeBlock(uint32_t size) | ||
{ | ||
BlockIdx block_idx = blocks_size_; | ||
ArrayBlock<TYPE> *block = new ArrayBlock<TYPE>(size); | ||
pushBlock(block); | ||
BlockIdx block_idx = blocks_.size(); | ||
pushBlock(size); | ||
free_block_idx_ = block_idx; | ||
// ObjectId zero is reserved for object_id_null. | ||
free_idx_ = (block_idx > 0) ? 0 : 1; | ||
return block; | ||
return &blocks_[block_idx]; | ||
} | ||
|
||
template <class TYPE> | ||
void | ||
ArrayTable<TYPE>::pushBlock(ArrayBlock<TYPE> *block) | ||
ArrayTable<TYPE>::pushBlock(size_t size) | ||
{ | ||
blocks_[blocks_size_++] = block; | ||
if (blocks_size_ >= block_id_max) | ||
blocks_.push_back(ArrayBlock<TYPE>(size)); | ||
|
||
if (blocks_.size() >= block_id_max) | ||
criticalError(223, "max array table block count exceeded."); | ||
if (blocks_size_ == blocks_capacity_) { | ||
size_t new_capacity = blocks_capacity_ * 1.5; | ||
ArrayBlock<TYPE>** new_blocks = new ArrayBlock<TYPE>*[new_capacity]; | ||
memcpy(new_blocks, blocks_, blocks_capacity_ * sizeof(ArrayBlock<TYPE>*)); | ||
if (prev_blocks_) | ||
delete [] prev_blocks_; | ||
if (blocks_.size() == blocks_.capacity()) { | ||
prev_blocks_.reserve(blocks_.capacity() * 1.5); | ||
const auto blocks_mid = blocks_.begin() + prev_blocks_.size(); | ||
std::copy(blocks_.begin(), blocks_mid, prev_blocks_.begin()); | ||
prev_blocks_.insert(prev_blocks_.end(), blocks_mid, blocks_.end()); | ||
// Preserve block array for other threads to reference. | ||
prev_blocks_ = blocks_; | ||
blocks_ = new_blocks; | ||
blocks_capacity_ = new_capacity; | ||
// Swap the vectors while keeping blocks_ valid. | ||
// vector::swap is usually basically the same, | ||
// but a naive implementation might temporarily set blocks_ to null. | ||
const size_t vec_size = sizeof(blocks_); | ||
std::uint8_t tmp[vec_size]; | ||
std::memcpy(&tmp, static_cast<void*>(&blocks_), vec_size); | ||
std::memcpy(static_cast<void*>(&blocks_), static_cast<void*>(&prev_blocks_), vec_size); | ||
std::memcpy(static_cast<void*>(&prev_blocks_), &tmp, vec_size); | ||
} | ||
} | ||
|
||
|
@@ -191,14 +188,14 @@ ArrayTable<TYPE>::destroy(ObjectId id, | |
|
||
template <class TYPE> | ||
TYPE * | ||
ArrayTable<TYPE>::pointer(ObjectId id) const | ||
ArrayTable<TYPE>::pointer(ObjectId id) | ||
{ | ||
if (id == object_id_null) | ||
return nullptr; | ||
else { | ||
BlockIdx blk_idx = id >> idx_bits; | ||
ObjectIdx obj_idx = id & idx_mask_; | ||
return blocks_[blk_idx]->pointer(obj_idx); | ||
return blocks_[blk_idx].pointer(obj_idx); | ||
} | ||
} | ||
|
||
|
@@ -209,31 +206,29 @@ ArrayTable<TYPE>::ensureId(ObjectId id) | |
BlockIdx blk_idx = id >> idx_bits; | ||
ObjectIdx obj_idx = id & idx_mask_; | ||
// Make enough blocks for blk_idx to be valid. | ||
for (BlockIdx i = blocks_size_; i <= blk_idx; i++) { | ||
ArrayBlock<TYPE> *block = new ArrayBlock<TYPE>(block_size); | ||
pushBlock(block); | ||
for (BlockIdx i = blocks_.size(); i <= blk_idx; i++) { | ||
pushBlock(block_size); | ||
} | ||
return blocks_[blk_idx]->pointer(obj_idx); | ||
return blocks_[blk_idx].pointer(obj_idx); | ||
} | ||
|
||
template <class TYPE> | ||
TYPE & | ||
ArrayTable<TYPE>::ref(ObjectId id) const | ||
ArrayTable<TYPE>::ref(ObjectId id) | ||
{ | ||
if (id == object_id_null) | ||
criticalError(222, "null ObjectId reference is undefined."); | ||
|
||
BlockIdx blk_idx = id >> idx_bits; | ||
ObjectIdx obj_idx = id & idx_mask_; | ||
return blocks_[blk_idx]->ref(obj_idx); | ||
return blocks_[blk_idx].ref(obj_idx); | ||
} | ||
|
||
template <class TYPE> | ||
void | ||
ArrayTable<TYPE>::clear() | ||
{ | ||
deleteBlocks(); | ||
blocks_size_ = 0; | ||
size_ = 0; | ||
free_block_idx_ = block_idx_null; | ||
free_idx_ = object_idx_null; | ||
|
@@ -246,11 +241,12 @@ template <class TYPE> | |
class ArrayBlock | ||
{ | ||
public: | ||
ArrayBlock() = default; | ||
ArrayBlock(uint32_t size); | ||
~ArrayBlock(); | ||
uint32_t size() const { return size_; } | ||
TYPE &ref(ObjectIdx idx) { return objects_[idx]; } | ||
TYPE *pointer(ObjectIdx idx) { return &objects_[idx]; } | ||
void free() { delete[] objects_; } | ||
|
||
private: | ||
uint32_t size_; | ||
|
@@ -264,10 +260,4 @@ ArrayBlock<TYPE>::ArrayBlock(uint32_t size) : | |
{ | ||
} | ||
|
||
template <class TYPE> | ||
ArrayBlock<TYPE>::~ArrayBlock() | ||
{ | ||
delete [] objects_; | ||
} | ||
|
||
} // Namespace |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loss of one indirection means we can't use
const
here, but if we're getting non-const pointers to vertices, then I would argue the graph is not actually const.