mirror of https://github.com/grpc/grpc.git
Chunked vector type (#27517)
* chunked vector type * Automated change: Fix sanity tests * compile fix Co-authored-by: ctiller <ctiller@users.noreply.github.com>reviewable/pr27526/r1
parent
04ce30a1f8
commit
253d7076fc
10 changed files with 877 additions and 0 deletions
@ -0,0 +1,207 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef GRPC_CORE_LIB_GPRPP_CHUNKED_VECTOR_H |
||||
#define GRPC_CORE_LIB_GPRPP_CHUNKED_VECTOR_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/gprpp/arena.h" |
||||
#include "src/core/lib/gprpp/manual_constructor.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// Arena-friendly vector type.
|
||||
// This "vector" allocates non-contiguous runs of kChunkSize T's at a time.
|
||||
// Expectation is that most usage will fit in one chunk, sometimes two will be
|
||||
// needed, and very rarely three. Appending is constant time, calculating the
|
||||
// size is O(n_chunks).
|
||||
template <typename T, size_t kChunkSize> |
||||
class ChunkedVector { |
||||
private: |
||||
// One chunk of allocated memory.
|
||||
struct Chunk { |
||||
Chunk() = default; |
||||
Chunk* next = nullptr; |
||||
size_t count = 0; |
||||
ManualConstructor<T> data[kChunkSize]; |
||||
}; |
||||
|
||||
public: |
||||
explicit ChunkedVector(Arena* arena) : arena_(arena) {} |
||||
template <class Iterator> |
||||
ChunkedVector(Arena* arena, Iterator begin, Iterator end) : arena_(arena) { |
||||
for (; begin != end; ++begin) { |
||||
EmplaceBack(*begin); |
||||
} |
||||
} |
||||
ChunkedVector(const ChunkedVector& other) |
||||
: ChunkedVector(other.arena_, other.begin(), other.end()) {} |
||||
ChunkedVector& operator=(ChunkedVector other) { |
||||
Swap(&other); |
||||
return *this; |
||||
} |
||||
ChunkedVector(ChunkedVector&& other) noexcept |
||||
: arena_(other.arena_), first_(other.first_), append_(other.append_) { |
||||
other.first_ = nullptr; |
||||
other.append_ = nullptr; |
||||
} |
||||
ChunkedVector& operator=(ChunkedVector&& other) noexcept { |
||||
Swap(&other); |
||||
return *this; |
||||
} |
||||
~ChunkedVector() { Clear(); } |
||||
void Swap(ChunkedVector* other) { |
||||
std::swap(other->arena_, arena_); |
||||
std::swap(other->first_, first_); |
||||
std::swap(other->append_, append_); |
||||
} |
||||
|
||||
// Append a new element to the end of the vector.
|
||||
template <typename... Args> |
||||
void EmplaceBack(Args&&... args) { |
||||
AppendSlot()->Init(std::forward<Args>(args)...); |
||||
} |
||||
|
||||
// Remove the last element and return it.
|
||||
T PopBack() { |
||||
GPR_ASSERT(append_ != nullptr); |
||||
if (append_->count == 0) { |
||||
GPR_ASSERT(first_ != append_); |
||||
Chunk* chunk = first_; |
||||
while (chunk->next != append_) { |
||||
chunk = chunk->next; |
||||
} |
||||
append_ = chunk; |
||||
} |
||||
const auto last = append_->count - 1; |
||||
T result = std::move(*append_->data[last]); |
||||
append_->data[last].Destroy(); |
||||
append_->count = last; |
||||
return result; |
||||
} |
||||
|
||||
void Clear() { |
||||
Chunk* chunk = first_; |
||||
while (chunk != nullptr && chunk->count != 0) { |
||||
for (size_t i = 0; i < chunk->count; i++) { |
||||
chunk->data[i].Destroy(); |
||||
} |
||||
chunk->count = 0; |
||||
chunk = chunk->next; |
||||
} |
||||
append_ = first_; |
||||
} |
||||
|
||||
// Forward-only iterator.
|
||||
class ForwardIterator { |
||||
public: |
||||
ForwardIterator(Chunk* chunk, size_t n) : chunk_(chunk), n_(n) {} |
||||
|
||||
T& operator*() const { return *chunk_->data[n_]; } |
||||
T* operator->() const { return &*chunk_->data[n_]; } |
||||
ForwardIterator& operator++() { |
||||
++n_; |
||||
if (n_ == chunk_->count) { |
||||
chunk_ = chunk_->next; |
||||
n_ = 0; |
||||
} |
||||
return *this; |
||||
} |
||||
bool operator==(const ForwardIterator& other) const { |
||||
return chunk_ == other.chunk_ && n_ == other.n_; |
||||
} |
||||
bool operator!=(const ForwardIterator& other) const { |
||||
return !(*this == other); |
||||
} |
||||
|
||||
private: |
||||
Chunk* chunk_; |
||||
size_t n_; |
||||
}; |
||||
|
||||
// Const Forward-only iterator.
|
||||
class ConstForwardIterator { |
||||
public: |
||||
ConstForwardIterator(const Chunk* chunk, size_t n) : chunk_(chunk), n_(n) {} |
||||
|
||||
const T& operator*() const { return *chunk_->data[n_]; } |
||||
const T* operator->() const { return &*chunk_->data[n_]; } |
||||
ConstForwardIterator& operator++() { |
||||
++n_; |
||||
if (n_ == chunk_->count) { |
||||
chunk_ = chunk_->next; |
||||
n_ = 0; |
||||
} |
||||
return *this; |
||||
} |
||||
bool operator==(const ConstForwardIterator& other) const { |
||||
return chunk_ == other.chunk_ && n_ == other.n_; |
||||
} |
||||
bool operator!=(const ConstForwardIterator& other) const { |
||||
return !(*this == other); |
||||
} |
||||
|
||||
private: |
||||
const Chunk* chunk_; |
||||
size_t n_; |
||||
}; |
||||
|
||||
ForwardIterator begin() { |
||||
if (first_ != nullptr && first_->count == 0) return end(); |
||||
return ForwardIterator(first_, 0); |
||||
} |
||||
ForwardIterator end() { return ForwardIterator(nullptr, 0); } |
||||
|
||||
ConstForwardIterator begin() const { |
||||
if (first_ != nullptr && first_->count == 0) return cend(); |
||||
return ConstForwardIterator(first_, 0); |
||||
} |
||||
ConstForwardIterator end() const { return ConstForwardIterator(nullptr, 0); } |
||||
|
||||
ConstForwardIterator cbegin() const { return begin(); } |
||||
ConstForwardIterator cend() const { return end(); } |
||||
|
||||
// Count the number of elements in the vector.
|
||||
size_t size() const { |
||||
size_t n = 0; |
||||
for (const Chunk* chunk = first_; chunk != nullptr; chunk = chunk->next) { |
||||
n += chunk->count; |
||||
} |
||||
return n; |
||||
} |
||||
|
||||
private: |
||||
ManualConstructor<T>* AppendSlot() { |
||||
if (append_ == nullptr) { |
||||
GPR_ASSERT(first_ == nullptr); |
||||
first_ = arena_->New<Chunk>(); |
||||
append_ = first_; |
||||
} else if (append_->count == kChunkSize) { |
||||
if (append_->next == nullptr) { |
||||
append_->next = arena_->New<Chunk>(); |
||||
} |
||||
append_ = append_->next; |
||||
} |
||||
return &append_->data[append_->count++]; |
||||
} |
||||
|
||||
Arena* arena_; |
||||
Chunk* first_ = nullptr; |
||||
Chunk* append_ = nullptr; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_LIB_GPRPP_CHUNKED_VECTOR_H
|
@ -0,0 +1,14 @@ |
||||
actions { |
||||
move { |
||||
from: 875700282 |
||||
to: 3473408 |
||||
} |
||||
} |
||||
actions { |
||||
emplace_back { |
||||
} |
||||
} |
||||
actions { |
||||
clear { |
||||
} |
||||
} |
@ -0,0 +1,153 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <vector> |
||||
|
||||
#include "src/core/lib/gprpp/chunked_vector.h" |
||||
#include "src/libfuzzer/libfuzzer_macro.h" |
||||
#include "test/core/gprpp/chunked_vector_fuzzer.pb.h" |
||||
|
||||
bool squelch = true; |
||||
bool leak_check = true; |
||||
|
||||
static constexpr size_t kChunkSize = 17; |
||||
using IntHdl = std::shared_ptr<int>; |
||||
|
||||
namespace grpc_core { |
||||
struct Comparison { |
||||
explicit Comparison(Arena* arena) : chunked(arena) {} |
||||
|
||||
ChunkedVector<IntHdl, kChunkSize> chunked; |
||||
std::vector<IntHdl> std; |
||||
|
||||
// Check that both chunked and std are equivalent.
|
||||
void AssertOk() const { |
||||
GPR_ASSERT(std.size() == chunked.size()); |
||||
auto it_chunked = chunked.cbegin(); |
||||
auto it_std = std.cbegin(); |
||||
while (it_std != std.cend()) { |
||||
GPR_ASSERT(**it_std == **it_chunked); |
||||
++it_chunked; |
||||
++it_std; |
||||
} |
||||
GPR_ASSERT(it_chunked == chunked.cend()); |
||||
} |
||||
}; |
||||
|
||||
class Fuzzer { |
||||
public: |
||||
Fuzzer() : arena_(Arena::Create(128)) {} |
||||
~Fuzzer() { |
||||
vectors_.clear(); |
||||
arena_->Destroy(); |
||||
} |
||||
|
||||
void Act(const chunked_vector_fuzzer::Action& action) { |
||||
switch (action.action_type_case()) { |
||||
case chunked_vector_fuzzer::Action::kEmplaceBack: { |
||||
// Add some value to the back of a comparison, assert that both vectors
|
||||
// are equivalent.
|
||||
auto* c = Mutate(action.emplace_back().vector()); |
||||
c->chunked.EmplaceBack( |
||||
std::make_shared<int>(action.emplace_back().value())); |
||||
c->std.emplace_back( |
||||
std::make_shared<int>(action.emplace_back().value())); |
||||
c->AssertOk(); |
||||
} break; |
||||
case chunked_vector_fuzzer::Action::kPopBack: { |
||||
// Remove some value to the back of a comparison, assert that both
|
||||
// vectors are equivalent.
|
||||
auto* c = Mutate(action.pop_back().vector()); |
||||
if (c->chunked.size() > 0) { |
||||
c->chunked.PopBack(); |
||||
c->std.pop_back(); |
||||
c->AssertOk(); |
||||
} |
||||
} break; |
||||
case chunked_vector_fuzzer::Action::kCopy: { |
||||
// Copy one vector into another, assert both everything stays
|
||||
// equivalent.
|
||||
auto it_from = vectors_.find(action.copy().from()); |
||||
if (it_from == vectors_.end()) { |
||||
it_from = |
||||
vectors_.emplace(action.copy().from(), Comparison(arena_)).first; |
||||
} |
||||
auto it_to = vectors_.find(action.copy().to()); |
||||
if (it_to == vectors_.end()) { |
||||
it_to = vectors_.emplace(action.copy().to(), it_from->second).first; |
||||
} else { |
||||
it_to->second = it_from->second; |
||||
} |
||||
it_from->second.AssertOk(); |
||||
it_to->second.AssertOk(); |
||||
} break; |
||||
case chunked_vector_fuzzer::Action::kMove: { |
||||
// Move one vector into another, assert both everything stays
|
||||
// equivalent.
|
||||
auto it_from = vectors_.find(action.move().from()); |
||||
if (it_from == vectors_.end()) { |
||||
it_from = |
||||
vectors_.emplace(action.move().from(), Comparison(arena_)).first; |
||||
} |
||||
auto it_to = vectors_.find(action.move().to()); |
||||
if (it_to == vectors_.end()) { |
||||
it_to = |
||||
vectors_.emplace(action.move().to(), std::move(it_from->second)) |
||||
.first; |
||||
} else { |
||||
it_to->second = it_from->second; |
||||
} |
||||
it_from->second.AssertOk(); |
||||
it_to->second.AssertOk(); |
||||
} break; |
||||
case chunked_vector_fuzzer::Action::kClear: { |
||||
// Clear a vector, assert that both underlying vectors are equivalent.
|
||||
auto* c = Mutate(action.clear().vector()); |
||||
c->chunked.Clear(); |
||||
c->std.clear(); |
||||
c->AssertOk(); |
||||
} break; |
||||
case chunked_vector_fuzzer::Action::kSwap: { |
||||
// Swap two vectors, assert that both underlying vectors are equivalent.
|
||||
auto* from = Mutate(action.swap().from()); |
||||
auto* to = Mutate(action.swap().to()); |
||||
from->chunked.Swap(&to->chunked); |
||||
from->std.swap(to->std); |
||||
from->AssertOk(); |
||||
} break; |
||||
case chunked_vector_fuzzer::Action::ACTION_TYPE_NOT_SET: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
private: |
||||
Comparison* Mutate(int index) { |
||||
auto it = vectors_.find(index); |
||||
if (it != vectors_.end()) { |
||||
return &it->second; |
||||
} |
||||
return &vectors_.emplace(index, Comparison(arena_)).first->second; |
||||
} |
||||
|
||||
Arena* arena_; |
||||
std::map<int, Comparison> vectors_; |
||||
}; |
||||
} // namespace grpc_core
|
||||
|
||||
DEFINE_PROTO_FUZZER(const chunked_vector_fuzzer::Msg& msg) { |
||||
grpc_core::Fuzzer fuzzer; |
||||
for (int i = 0; i < msg.actions_size(); i++) { |
||||
fuzzer.Act(msg.actions(i)); |
||||
} |
||||
} |
@ -0,0 +1,50 @@ |
||||
// Copyright 2021 gRPC authors. |
||||
// |
||||
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||
// you may not use this file except in compliance with the License. |
||||
// You may obtain a copy of the License at |
||||
// |
||||
// http://www.apache.org/licenses/LICENSE-2.0 |
||||
// |
||||
// Unless required by applicable law or agreed to in writing, software |
||||
// distributed under the License is distributed on an "AS IS" BASIS, |
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
// See the License for the specific language governing permissions and |
||||
// limitations under the License. |
||||
syntax = "proto3"; |
||||
|
||||
package chunked_vector_fuzzer; |
||||
|
||||
message EmplaceBack { |
||||
int32 vector = 1; |
||||
int32 value = 2; |
||||
} |
||||
|
||||
message PopBack { |
||||
int32 vector = 1; |
||||
int32 value = 2; |
||||
} |
||||
|
||||
message Copy { |
||||
int32 from = 1; |
||||
int32 to = 2; |
||||
} |
||||
|
||||
message ClearVector { |
||||
int32 vector = 1; |
||||
} |
||||
|
||||
message Action { |
||||
oneof action_type { |
||||
EmplaceBack emplace_back = 1; |
||||
PopBack pop_back = 2; |
||||
Copy copy = 3; |
||||
Copy move = 4; |
||||
ClearVector clear = 5; |
||||
Copy swap = 6; |
||||
} |
||||
} |
||||
|
||||
message Msg { |
||||
repeated Action actions = 1; |
||||
} |
@ -0,0 +1,169 @@ |
||||
// Copyright 2021 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/core/lib/gprpp/chunked_vector.h" |
||||
|
||||
#include <gtest/gtest.h> |
||||
|
||||
namespace grpc_core { |
||||
namespace testing { |
||||
|
||||
static constexpr size_t kInitialArenaSize = 1024; |
||||
static constexpr size_t kChunkSize = 3; |
||||
|
||||
TEST(ChunkedVector, Noop) { |
||||
auto* arena = Arena::Create(kInitialArenaSize); |
||||
{ |
||||
ChunkedVector<int, kChunkSize> v(arena); |
||||
EXPECT_EQ(0, v.size()); |
||||
} |
||||
arena->Destroy(); |
||||
} |
||||
|
||||
TEST(ChunkedVector, Stack) { |
||||
auto* arena = Arena::Create(kInitialArenaSize); |
||||
{ |
||||
ChunkedVector<int, kChunkSize> v(arena); |
||||
|
||||
// Populate 2 chunks of memory, and 2/3 of a final chunk.
|
||||
EXPECT_EQ(0, v.size()); |
||||
v.EmplaceBack(1); |
||||
EXPECT_EQ(1, v.size()); |
||||
v.EmplaceBack(2); |
||||
EXPECT_EQ(2, v.size()); |
||||
v.EmplaceBack(3); |
||||
EXPECT_EQ(3, v.size()); |
||||
v.EmplaceBack(4); |
||||
EXPECT_EQ(4, v.size()); |
||||
v.EmplaceBack(5); |
||||
EXPECT_EQ(5, v.size()); |
||||
v.EmplaceBack(6); |
||||
EXPECT_EQ(6, v.size()); |
||||
v.EmplaceBack(7); |
||||
EXPECT_EQ(7, v.size()); |
||||
v.EmplaceBack(8); |
||||
EXPECT_EQ(8, v.size()); |
||||
|
||||
// Now pop all of them out and check the expected ordering.
|
||||
EXPECT_EQ(8, v.PopBack()); |
||||
EXPECT_EQ(7, v.size()); |
||||
EXPECT_EQ(7, v.PopBack()); |
||||
EXPECT_EQ(6, v.size()); |
||||
EXPECT_EQ(6, v.PopBack()); |
||||
EXPECT_EQ(5, v.size()); |
||||
EXPECT_EQ(5, v.PopBack()); |
||||
EXPECT_EQ(4, v.size()); |
||||
EXPECT_EQ(4, v.PopBack()); |
||||
EXPECT_EQ(3, v.size()); |
||||
EXPECT_EQ(3, v.PopBack()); |
||||
EXPECT_EQ(2, v.size()); |
||||
EXPECT_EQ(2, v.PopBack()); |
||||
EXPECT_EQ(1, v.size()); |
||||
EXPECT_EQ(1, v.PopBack()); |
||||
EXPECT_EQ(0, v.size()); |
||||
} |
||||
arena->Destroy(); |
||||
} |
||||
|
||||
TEST(ChunkedVector, Iterate) { |
||||
auto* arena = Arena::Create(kInitialArenaSize); |
||||
{ |
||||
ChunkedVector<int, kChunkSize> v(arena); |
||||
v.EmplaceBack(1); |
||||
v.EmplaceBack(2); |
||||
v.EmplaceBack(3); |
||||
v.EmplaceBack(4); |
||||
v.EmplaceBack(5); |
||||
v.EmplaceBack(6); |
||||
v.EmplaceBack(7); |
||||
v.EmplaceBack(8); |
||||
|
||||
auto it = v.begin(); |
||||
EXPECT_EQ(1, *it); |
||||
++it; |
||||
EXPECT_EQ(2, *it); |
||||
++it; |
||||
EXPECT_EQ(3, *it); |
||||
++it; |
||||
EXPECT_EQ(4, *it); |
||||
++it; |
||||
EXPECT_EQ(5, *it); |
||||
++it; |
||||
EXPECT_EQ(6, *it); |
||||
++it; |
||||
EXPECT_EQ(7, *it); |
||||
++it; |
||||
EXPECT_EQ(8, *it); |
||||
++it; |
||||
EXPECT_EQ(v.end(), it); |
||||
} |
||||
arena->Destroy(); |
||||
} |
||||
|
||||
TEST(ChunkedVector, ConstIterate) { |
||||
auto* arena = Arena::Create(kInitialArenaSize); |
||||
{ |
||||
ChunkedVector<int, kChunkSize> v(arena); |
||||
v.EmplaceBack(1); |
||||
v.EmplaceBack(2); |
||||
v.EmplaceBack(3); |
||||
v.EmplaceBack(4); |
||||
v.EmplaceBack(5); |
||||
v.EmplaceBack(6); |
||||
v.EmplaceBack(7); |
||||
v.EmplaceBack(8); |
||||
|
||||
auto it = v.cbegin(); |
||||
EXPECT_EQ(1, *it); |
||||
++it; |
||||
EXPECT_EQ(2, *it); |
||||
++it; |
||||
EXPECT_EQ(3, *it); |
||||
++it; |
||||
EXPECT_EQ(4, *it); |
||||
++it; |
||||
EXPECT_EQ(5, *it); |
||||
++it; |
||||
EXPECT_EQ(6, *it); |
||||
++it; |
||||
EXPECT_EQ(7, *it); |
||||
++it; |
||||
EXPECT_EQ(8, *it); |
||||
++it; |
||||
EXPECT_EQ(v.cend(), it); |
||||
} |
||||
arena->Destroy(); |
||||
} |
||||
|
||||
TEST(ChunkedVector, Clear) { |
||||
auto* arena = Arena::Create(kInitialArenaSize); |
||||
{ |
||||
ChunkedVector<int, kChunkSize> v(arena); |
||||
v.EmplaceBack(1); |
||||
EXPECT_EQ(v.size(), 1); |
||||
v.Clear(); |
||||
EXPECT_EQ(v.size(), 0); |
||||
EXPECT_EQ(v.begin(), v.end()); |
||||
} |
||||
arena->Destroy(); |
||||
} |
||||
|
||||
} // namespace testing
|
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue