Skip to content
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

[NetKAT] Add PagedStableVector class that provides pointer stability. #19

Merged
merged 1 commit into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions netkat/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ cc_library(
],
)

cc_test(
name = "evaluator_test",
srcs = ["evaluator_test.cc"],
deps = [
":evaluator",
":netkat_cc_proto",
":netkat_proto_constructors",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_fuzztest//fuzztest",
"@com_google_googletest//:gtest_main",
],
)

cc_library(
name = "netkat_proto_constructors",
srcs = ["netkat_proto_constructors.cc"],
Expand All @@ -82,14 +95,16 @@ cc_test(
],
)

cc_library(
name = "paged_stable_vector",
hdrs = ["paged_stable_vector.h"],
)

cc_test(
name = "evaluator_test",
srcs = ["evaluator_test.cc"],
name = "paged_stable_vector_test",
srcs = ["paged_stable_vector_test.cc"],
deps = [
":evaluator",
":netkat_cc_proto",
":netkat_proto_constructors",
"@com_google_absl//absl/container:flat_hash_set",
":paged_stable_vector",
"@com_google_fuzztest//fuzztest",
"@com_google_googletest//:gtest_main",
],
Expand Down
74 changes: 74 additions & 0 deletions netkat/paged_stable_vector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2024 The NetKAT 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
//
// https://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.
//
// -----------------------------------------------------------------------------
// File: paged_stable_vector.h
// -----------------------------------------------------------------------------

#ifndef GOOGLE_NETKAT_NETKAT_PAGED_STABLE_VECTOR_H_
#define GOOGLE_NETKAT_NETKAT_PAGED_STABLE_VECTOR_H_

#include <cstddef>
#include <utility>
#include <vector>

namespace netkat {

// A variant of `std::vector` that allocates memory in pages (or "chunks") of
// fixed `PageSize`. This introduces an extra level of indirection and
// introduces some level of discontiguity (depending on `PageSize`), but allows
// the class to guarantee pointer stability: calls to `push_back`/`emplace_back`
// never invalidate pointers/iterators/references to elements previously added
// to the vector.
//
// Allocating memory in pages also avoids the cost of relocation, which may be
// significant for very large vectors in performance-sensitive applications.
//
// The API of this class is kept just large enough to cover our use cases.
template <class T, size_t PageSize>
class PagedStableVector {
public:
PagedStableVector() = default;

size_t size() const {
return data_.empty() ? 0
: (data_.size() - 1) * PageSize + data_.back().size();
}

template <class Value>
void push_back(Value&& value) {
if (size() % PageSize == 0) data_.emplace_back().reserve(PageSize);
data_.back().push_back(std::forward<Value>(value));
}

template <class... Args>
void emplace_back(Args&&... value) {
if (size() % PageSize == 0) data_.emplace_back().reserve(PageSize);
data_.back().emplace_back(std::forward<Args>(value)...);
}

T& operator[](size_t index) {
return data_[index / PageSize][index % PageSize];
}
const T& operator[](size_t index) const {
return data_[index / PageSize][index % PageSize];
}

private:
std::vector<std::vector<T>> data_;
};

} // namespace netkat

#endif // GOOGLE_NETKAT_NETKAT_PAGED_STABLE_VECTOR_H_
105 changes: 105 additions & 0 deletions netkat/paged_stable_vector_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2024 The NetKAT 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
//
// https://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 "netkat/paged_stable_vector.h"

#include <string>
#include <vector>

#include "fuzztest/fuzztest.h"
#include "gtest/gtest.h"

namespace netkat {
namespace {

// A small, but otherwise random page size used throughout the tests.
// Using a small page size is useful for exercising the page replacement logic.
static constexpr int kSmallPageSize = 3;

void PushBackInreasesSize(std::vector<std::string> elements) {
PagedStableVector<std::string, kSmallPageSize> vector;
for (const auto& element : elements) {
vector.push_back(element);
}
EXPECT_EQ(vector.size(), elements.size());
}
FUZZ_TEST(PagedStableVectorTest, PushBackInreasesSize);

void EmplaceBackInreasesSize(std::vector<int> elements) {
PagedStableVector<int, kSmallPageSize> vector;
for (const auto& element : elements) {
vector.emplace_back(element);
}
EXPECT_EQ(vector.size(), elements.size());
}
FUZZ_TEST(PagedStableVectorTest, EmplaceBackInreasesSize);

void PushBackAddsElementToBack(std::vector<std::string> elements) {
PagedStableVector<std::string, kSmallPageSize> vector;
for (int i = 0; i < elements.size(); ++i) {
vector.push_back(elements[i]);
for (int j = 0; j < i; ++j) {
EXPECT_EQ(vector[j], elements[j]);
}
}
}
FUZZ_TEST(PagedStableVectorTest, PushBackAddsElementToBack);

void EmplaceBackAddsElementToBack(std::vector<int> elements) {
PagedStableVector<int, kSmallPageSize> vector;
for (int i = 0; i < elements.size(); ++i) {
vector.emplace_back(elements[i]);
for (int j = 0; j < i; ++j) {
EXPECT_EQ(vector[j], elements[j]);
}
}
}
FUZZ_TEST(PagedStableVectorTest, EmplaceBackAddsElementToBack);

void BracketAssigmentWorks(std::vector<std::string> elements) {
PagedStableVector<std::string, kSmallPageSize> vector;
for (int i = 0; i < elements.size(); ++i) {
vector.push_back("initial value");
}
for (int i = 0; i < elements.size(); ++i) {
vector[i] = elements[i];
}
for (int i = 0; i < elements.size(); ++i) {
EXPECT_EQ(vector[i], elements[i]);
}
}
FUZZ_TEST(PagedStableVectorTest, BracketAssigmentWorks);

TEST(PagedStableVectorTest, ReferencesDontGetInvalidated) {
PagedStableVector<std::string, kSmallPageSize> vector;

// Store a few references.
vector.push_back("first element");
std::string* first_element_ptr = &vector[0];
vector.push_back("second element");
std::string* second_element_ptr = &vector[1];

// Push a ton of elements to trigger page allocation.
// If this were a regular std::vector, the references would be invalidated.
for (int i = 0; i < 100 * kSmallPageSize; ++i) {
vector.push_back("dummy");
}

// Check that the references are still valid.
EXPECT_EQ(&vector[0], first_element_ptr);
EXPECT_EQ(&vector[1], second_element_ptr);
};

} // namespace
} // namespace netkat
Loading