From efdcd02f50b41dc16df85358a2afe6c66b3f8b7b Mon Sep 17 00:00:00 2001 From: Steffen Smolka Date: Fri, 13 Dec 2024 17:44:33 -0800 Subject: [PATCH] [NetKAT] Add PagedStableVector class that provides pointer stability. PiperOrigin-RevId: 706058715 --- netkat/BUILD.bazel | 27 ++++++-- netkat/paged_stable_vector.h | 74 ++++++++++++++++++++ netkat/paged_stable_vector_test.cc | 105 +++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 netkat/paged_stable_vector.h create mode 100644 netkat/paged_stable_vector_test.cc diff --git a/netkat/BUILD.bazel b/netkat/BUILD.bazel index 69b5820..1202e85 100644 --- a/netkat/BUILD.bazel +++ b/netkat/BUILD.bazel @@ -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"], @@ -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", ], diff --git a/netkat/paged_stable_vector.h b/netkat/paged_stable_vector.h new file mode 100644 index 0000000..04ac895 --- /dev/null +++ b/netkat/paged_stable_vector.h @@ -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 +#include +#include + +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 PagedStableVector { + public: + PagedStableVector() = default; + + size_t size() const { + return data_.empty() ? 0 + : (data_.size() - 1) * PageSize + data_.back().size(); + } + + template + void push_back(Value&& value) { + if (size() % PageSize == 0) data_.emplace_back().reserve(PageSize); + data_.back().push_back(std::forward(value)); + } + + template + void emplace_back(Args&&... value) { + if (size() % PageSize == 0) data_.emplace_back().reserve(PageSize); + data_.back().emplace_back(std::forward(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> data_; +}; + +} // namespace netkat + +#endif // GOOGLE_NETKAT_NETKAT_PAGED_STABLE_VECTOR_H_ diff --git a/netkat/paged_stable_vector_test.cc b/netkat/paged_stable_vector_test.cc new file mode 100644 index 0000000..5b73580 --- /dev/null +++ b/netkat/paged_stable_vector_test.cc @@ -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 +#include + +#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 elements) { + PagedStableVector vector; + for (const auto& element : elements) { + vector.push_back(element); + } + EXPECT_EQ(vector.size(), elements.size()); +} +FUZZ_TEST(PagedStableVectorTest, PushBackInreasesSize); + +void EmplaceBackInreasesSize(std::vector elements) { + PagedStableVector vector; + for (const auto& element : elements) { + vector.emplace_back(element); + } + EXPECT_EQ(vector.size(), elements.size()); +} +FUZZ_TEST(PagedStableVectorTest, EmplaceBackInreasesSize); + +void PushBackAddsElementToBack(std::vector elements) { + PagedStableVector 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 elements) { + PagedStableVector 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 elements) { + PagedStableVector 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 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