Skip to content

Commit

Permalink
Test that the oracular caches start a new caching cycle correctly.
Browse files Browse the repository at this point in the history
Specifically, a new cycle (i.e., a round of populating the cache with multiple
slabs) should only be performed when the cache is full and a new slab is
encountered that is not in the current cache; otherwise, this would imply an
unnecessary cache miss. This test just ensures that the caching is optimal.

Some work is required to implement this test for the OracularVariableSlabCache,
due to the differences between the actual/estimated sizes for each slab.
  • Loading branch information
LTLA committed Sep 26, 2024
1 parent e35a55b commit 3642863
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 9 deletions.
18 changes: 17 additions & 1 deletion tests/src/OracularSlabCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#include "tatami_chunked/OracularSlabCache.hpp"

#include <random>
#include <unordered_set>
#include <vector>
#include <algorithm>

class OracularSlabCacheTestMethods {
protected:
Expand Down Expand Up @@ -329,6 +332,7 @@ class OracularSlabCacheStressTest : public ::testing::TestWithParam<int>, public

TEST_P(OracularSlabCacheStressTest, Stressed) {
auto cache_size = GetParam();
int expected_max_cache = std::min(5, cache_size);

std::mt19937_64 rng(cache_size + 1);
std::vector<int> predictions(10000);
Expand All @@ -340,14 +344,26 @@ TEST_P(OracularSlabCacheStressTest, Stressed) {
int counter = 0;
int nalloc = 0;
int cycle = 1;
std::unordered_set<int> previous_chunks;

for (size_t i = 0; i < predictions.size(); ++i) {
int last_cycle = cycle;

auto out = next(cache, counter, nalloc, cycle);
EXPECT_EQ(out.first->chunk_id, predictions[i] / 10);
EXPECT_EQ(out.second, predictions[i] % 10);

auto cid = out.first->chunk_id;
if (cycle != last_cycle && last_cycle != 1) {
EXPECT_FALSE(previous_chunks.find(cid) != previous_chunks.end());
EXPECT_EQ(expected_max_cache, previous_chunks.size());
previous_chunks.clear();
}
previous_chunks.insert(cid);
EXPECT_LE(previous_chunks.size(), cache_size);
}

EXPECT_EQ(nalloc, std::min({ 5, cache_size }));
EXPECT_EQ(nalloc, expected_max_cache);
}

INSTANTIATE_TEST_SUITE_P(
Expand Down
17 changes: 16 additions & 1 deletion tests/src/OracularSubsettedSlabCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include <random>
#include <algorithm>
#include <unordered_set>
#include <vector>

class OracularSubsettedSlabCacheTestMethods {
protected:
Expand Down Expand Up @@ -354,6 +356,7 @@ class OracularSubsettedSlabCacheStressTest : public ::testing::TestWithParam<int

TEST_P(OracularSubsettedSlabCacheStressTest, Stressed) {
auto cache_size = GetParam();
int expected_max_cache = std::min(5, cache_size);

std::mt19937_64 rng(cache_size + 1);
std::vector<int> predictions(10000);
Expand All @@ -366,8 +369,11 @@ TEST_P(OracularSubsettedSlabCacheStressTest, Stressed) {
int counter = 0;
int nalloc = 0;
int cycle = 1;
std::unordered_set<int> previous_chunks;

for (size_t i = 0; i < predictions.size(); ++i) {
int last_cycle = cycle;

auto out = next(cache, counter, nalloc, cycle);
EXPECT_EQ(out.first->chunk_id, predictions[i] / 10);
EXPECT_EQ(out.second, predictions[i] % 10);
Expand All @@ -381,9 +387,18 @@ TEST_P(OracularSubsettedSlabCacheStressTest, Stressed) {
confirm_mapping_integrity(sub);
EXPECT_TRUE(sub.mapping.find(out.second) != sub.mapping.end());
}

auto cid = out.first->chunk_id;
if (cycle != last_cycle && last_cycle != 1) {
EXPECT_FALSE(previous_chunks.find(cid) != previous_chunks.end());
EXPECT_EQ(expected_max_cache, previous_chunks.size());
previous_chunks.clear();
}
previous_chunks.insert(cid);
EXPECT_LE(previous_chunks.size(), cache_size);
}

EXPECT_EQ(nalloc, std::min({ 5, cache_size }));
EXPECT_EQ(nalloc, expected_max_cache);
}

INSTANTIATE_TEST_SUITE_P(
Expand Down
54 changes: 47 additions & 7 deletions tests/src/OracularVariableSlabCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include "tatami_chunked/OracularVariableSlabCache.hpp"

#include <random>
#include <unordered_set>
#include <vector>

class OracularVariableSlabCacheTestMethods {
protected:
Expand Down Expand Up @@ -394,7 +396,6 @@ TEST_F(OracularVariableSlabCacheTest, ReusedContracted) {
EXPECT_EQ(nalloc, 3);
}


TEST_F(OracularVariableSlabCacheTest, ConsecutiveReuse) {
// This just checks that the short-circuiting works correctly for
// consecutive requests to the same slab.
Expand Down Expand Up @@ -454,12 +455,17 @@ TEST_F(OracularVariableSlabCacheTest, ConsecutiveReuse) {
EXPECT_EQ(out.second, predictions[i] % 10);
EXPECT_EQ(cache.get_used_size(), 60);
}

// Max of 3 slabs in circulation at any given time.
EXPECT_EQ(nalloc, 3);
}

class OracularVariableSlabCacheStressTest : public ::testing::TestWithParam<int>, public OracularVariableSlabCacheTestMethods {};
class OracularVariableSlabCacheStressTest : public ::testing::TestWithParam<std::tuple<int, bool> >, public OracularVariableSlabCacheTestMethods {};

TEST_P(OracularVariableSlabCacheStressTest, Stressed) {
auto cache_size = GetParam();
auto params = GetParam();
auto cache_size = std::get<0>(params);
auto is_complex = std::get<1>(params);

std::mt19937_64 rng(cache_size + 1);
std::vector<int> predictions(10000);
Expand All @@ -471,18 +477,52 @@ TEST_P(OracularVariableSlabCacheStressTest, Stressed) {
int counter = 0;
int nalloc = 0;
int cycle = 1;
std::unordered_set<int> previous_chunks;
int lower_cache_cost = 0, upper_cache_cost = 0;

for (size_t i = 0; i < predictions.size(); ++i) {
auto out = simple_next(cache, counter, nalloc, cycle);
int last_cycle = cycle;

std::pair<const TestSlab*, int> out;
int lower_slab_cost = 0, upper_slab_cost = 0;
if (is_complex) {
out = complex_next(cache, counter, nalloc, cycle);
lower_slab_cost = out.first->chunk_id * 5; // see the actual and estimated sizes in complex_next().
upper_slab_cost = out.first->chunk_id * 10;
} else {
out = simple_next(cache, counter, nalloc, cycle);
lower_slab_cost = out.first->chunk_id * 10; // actual and estimated sizes are the same in simple_next().
upper_slab_cost = lower_slab_cost;
}

EXPECT_EQ(out.first->chunk_id, predictions[i] / 10);
EXPECT_EQ(out.second, predictions[i] % 10);
}

EXPECT_TRUE(nalloc <= 5);
auto cid = out.first->chunk_id;
if (cycle != last_cycle && last_cycle != 1) {
EXPECT_FALSE(previous_chunks.find(cid) != previous_chunks.end());
EXPECT_GT(upper_slab_cost + upper_cache_cost, cache_size);
previous_chunks.clear();
upper_cache_cost = 0;
lower_cache_cost = 0;
}

if (previous_chunks.find(cid) == previous_chunks.end()) {
lower_cache_cost += lower_slab_cost;
upper_cache_cost += upper_slab_cost;
previous_chunks.insert(cid);
if (previous_chunks.size() != 1) { // if there's only one chunk, then it must be stored, regardless of the cost.
EXPECT_LE(lower_cache_cost, cache_size);
}
}
}
}

INSTANTIATE_TEST_SUITE_P(
OracularVariableSlabCache,
OracularVariableSlabCacheStressTest,
::testing::Values(30, 50, 100) // max cache size
::testing::Combine(
::testing::Values(30, 50, 100), // max cache size
::testing::Values(false, true) // whether it's optimal
)
);

0 comments on commit 3642863

Please sign in to comment.