Skip to content

Commit

Permalink
Placeholder for AutoHyperClockCache, more
Browse files Browse the repository at this point in the history
Summary:
* The plan is for AutoHyperClockCache to be selected when
HyperClockCacheOptions::estimated_entry_charge == 0, and in that case to
use a new configuration option min_avg_entry_charge for determining an
extreme case maximum size for the hash table. For the placeholder, a
hack is in place in HyperClockCacheOptions::MakeSharedCache() to make
the unit tests happy despite the new options not really making sense
with the current implementation.
* Mostly updating and refactoring tests to test both the current HCC
(internal name FixedHyperClockCache) and a placeholder for the new version
(internal name AutoHyperClockCache).
* Simplify some existing tests not to depend directly on cache type.
* Type-parameterize the shard-level unit tests, which unfortunately
requires more syntax like `this->` in places for disambiguation.
* Added means of choosing auto_hyper_clock_cache to cache_bench,
db_bench, and db_stress, including add to crash test.
* Add another templated class BaseHyperClockCache to reduce future
copy-paste
* Added ReportProblems support to cache_bench
* Added a DEBUG-level diagnostic to ReportProblems for the variance in
load factor throughout the table, which will become more of a concern
with linear hashing to be used in the Auto implementation.

Test Plan: Shouldn't be any meaningful changes yet to production code
or to what is tested, but there is temporary redundancy in testing
until the new implementation is plugged in.
  • Loading branch information
pdillinger committed Aug 10, 2023
1 parent 76ed9a3 commit 3055d4a
Show file tree
Hide file tree
Showing 12 changed files with 496 additions and 337 deletions.
37 changes: 31 additions & 6 deletions cache/cache_bench_tool.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "util/hash.h"
#include "util/mutexlock.h"
#include "util/random.h"
#include "util/stderr_logger.h"
#include "util/stop_watch.h"
#include "util/string_util.h"

Expand All @@ -49,6 +50,9 @@ DEFINE_double(resident_ratio, 0.25,
"Ratio of keys fitting in cache to keyspace.");
DEFINE_uint64(ops_per_thread, 2000000U, "Number of operations per thread.");
DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
DEFINE_uint32(value_bytes_estimate, 0,
"If > 0, overrides estimated_entry_charge or "
"min_avg_entry_charge depending on cache_type.");

DEFINE_uint32(skew, 5, "Degree of skew in key selection. 0 = no skew");
DEFINE_bool(populate_cache, true, "Populate cache before operations");
Expand Down Expand Up @@ -83,6 +87,8 @@ DEFINE_bool(early_exit, false,
DEFINE_bool(histograms, true,
"Whether to track and print histogram statistics.");

DEFINE_bool(report_problems, true, "Whether to ReportProblems() at the end.");

DEFINE_uint32(seed, 0, "Hashing/random seed to use. 0 = choose at random");

DEFINE_string(secondary_cache_uri, "",
Expand Down Expand Up @@ -299,11 +305,23 @@ class CacheBench {
if (FLAGS_cache_type == "clock_cache") {
fprintf(stderr, "Old clock cache implementation has been removed.\n");
exit(1);
} else if (FLAGS_cache_type == "hyper_clock_cache" ||
FLAGS_cache_type == "fixed_hyper_clock_cache") {
HyperClockCacheOptions opts(FLAGS_cache_size, FLAGS_value_bytes,
FLAGS_num_shard_bits);
} else if (EndsWith(FLAGS_cache_type, "hyper_clock_cache")) {
HyperClockCacheOptions opts(
FLAGS_cache_size, /*estimated_entry_charge=*/0, FLAGS_num_shard_bits);
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
if (FLAGS_cache_type == "fixed_hyper_clock_cache" ||
FLAGS_cache_type == "hyper_clock_cache") {
opts.estimated_entry_charge = FLAGS_value_bytes_estimate > 0
? FLAGS_value_bytes_estimate
: FLAGS_value_bytes;
} else if (FLAGS_cache_type == "auto_hyper_clock_cache") {
if (FLAGS_value_bytes_estimate > 0) {
opts.min_avg_entry_charge = FLAGS_value_bytes_estimate;
}
} else {
fprintf(stderr, "Cache type not supported.");
exit(1);
}
cache_ = opts.MakeSharedCache();
} else if (FLAGS_cache_type == "lru_cache") {
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits,
Expand Down Expand Up @@ -454,7 +472,14 @@ class CacheBench {
printf("%s", stats_hist.ToString().c_str());
}
}
printf("\n%s", stats_report.c_str());

if (FLAGS_report_problems) {
printf("\n");
std::shared_ptr<Logger> logger =
std::make_shared<StderrLogger>(InfoLogLevel::DEBUG_LEVEL);
cache_->ReportProblems(logger);
}
printf("%s", stats_report.c_str());

return true;
}
Expand Down Expand Up @@ -499,7 +524,7 @@ class CacheBench {
for (;;) {
if (shared->AllDone()) {
std::ostringstream ostr;
ostr << "Most recent cache entry stats:\n"
ostr << "\nMost recent cache entry stats:\n"
<< "Number of entries: " << total_entry_count << "\n"
<< "Table occupancy: " << table_occupancy << " / "
<< table_size << " = "
Expand Down
104 changes: 29 additions & 75 deletions cache/cache_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,11 @@ const Cache::CacheItemHelper kDumbHelper{
CacheEntryRole::kMisc,
[](Cache::ObjectPtr /*value*/, MemoryAllocator* /*alloc*/) {}};

const Cache::CacheItemHelper kEraseOnDeleteHelper1{
const Cache::CacheItemHelper kInvokeOnDeleteHelper{
CacheEntryRole::kMisc,
[](Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) {
Cache* cache = static_cast<Cache*>(value);
cache->Erase("foo");
}};

const Cache::CacheItemHelper kEraseOnDeleteHelper2{
CacheEntryRole::kMisc,
[](Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) {
Cache* cache = static_cast<Cache*>(value);
cache->Erase(EncodeKey16Bytes(1234));
auto& fn = *static_cast<std::function<void()>*>(value);
fn();
}};
} // anonymous namespace

Expand Down Expand Up @@ -180,8 +173,6 @@ std::string CacheTest::type_;
class LRUCacheTest : public CacheTest {};

TEST_P(CacheTest, UsageTest) {
auto type = GetParam();

// cache is std::shared_ptr and will be automatically cleaned up.
const size_t kCapacity = 100000;
auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
Expand All @@ -196,12 +187,7 @@ TEST_P(CacheTest, UsageTest) {
char value[10] = "abcdef";
// make sure everything will be cached
for (int i = 1; i < 100; ++i) {
std::string key;
if (type == kLRU) {
key = std::string(i, 'a');
} else {
key = EncodeKey(i);
}
std::string key = EncodeKey(i);
auto kv_size = key.size() + 5;
ASSERT_OK(cache->Insert(key, value, &kDumbHelper, kv_size));
ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, kv_size));
Expand All @@ -221,12 +207,7 @@ TEST_P(CacheTest, UsageTest) {

// make sure the cache will be overloaded
for (size_t i = 1; i < kCapacity; ++i) {
std::string key;
if (type == kLRU) {
key = std::to_string(i);
} else {
key = EncodeKey(static_cast<int>(1000 + i));
}
std::string key = EncodeKey(static_cast<int>(1000 + i));
ASSERT_OK(cache->Insert(key, value, &kDumbHelper, key.size() + 5));
ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, key.size() + 5));
}
Expand All @@ -246,16 +227,14 @@ TEST_P(CacheTest, UsageTest) {
}
}

// TODO: This test takes longer than expected on ClockCache. This is
// because the values size estimate at construction is too sloppy.
// TODO: This test takes longer than expected on FixedHyperClockCache.
// This is because the values size estimate at construction is too sloppy.
// Fix this.
// Why is it so slow? The cache is constructed with an estimate of 1, but
// then the charge is claimed to be 21. This will cause the hash table
// to be extremely sparse, which in turn means clock needs to scan too
// many slots to find victims.
TEST_P(CacheTest, PinnedUsageTest) {
auto type = GetParam();

// cache is std::shared_ptr and will be automatically cleaned up.
const size_t kCapacity = 200000;
auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
Expand All @@ -274,12 +253,7 @@ TEST_P(CacheTest, PinnedUsageTest) {
// Add entries. Unpin some of them after insertion. Then, pin some of them
// again. Check GetPinnedUsage().
for (int i = 1; i < 100; ++i) {
std::string key;
if (type == kLRU) {
key = std::string(i, 'a');
} else {
key = EncodeKey(i);
}
std::string key = EncodeKey(i);
auto kv_size = key.size() + 5;
Cache::Handle* handle;
Cache::Handle* handle_in_precise_cache;
Expand Down Expand Up @@ -320,12 +294,7 @@ TEST_P(CacheTest, PinnedUsageTest) {

// check that overloading the cache does not change the pinned usage
for (size_t i = 1; i < 2 * kCapacity; ++i) {
std::string key;
if (type == kLRU) {
key = std::to_string(i);
} else {
key = EncodeKey(static_cast<int>(1000 + i));
}
std::string key = EncodeKey(static_cast<int>(1000 + i));
ASSERT_OK(cache->Insert(key, value, &kDumbHelper, key.size() + 5));
ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, key.size() + 5));
}
Expand Down Expand Up @@ -515,20 +484,20 @@ TEST_P(CacheTest, EvictionPolicyRef) {
// Check whether the entries inserted in the beginning
// are evicted. Ones without extra ref are evicted and
// those with are not.
ASSERT_EQ(-1, Lookup(100));
ASSERT_EQ(-1, Lookup(101));
ASSERT_EQ(-1, Lookup(102));
ASSERT_EQ(-1, Lookup(103));
EXPECT_EQ(-1, Lookup(100));
EXPECT_EQ(-1, Lookup(101));
EXPECT_EQ(-1, Lookup(102));
EXPECT_EQ(-1, Lookup(103));

ASSERT_EQ(-1, Lookup(300));
ASSERT_EQ(-1, Lookup(301));
ASSERT_EQ(-1, Lookup(302));
ASSERT_EQ(-1, Lookup(303));
EXPECT_EQ(-1, Lookup(300));
EXPECT_EQ(-1, Lookup(301));
EXPECT_EQ(-1, Lookup(302));
EXPECT_EQ(-1, Lookup(303));

ASSERT_EQ(101, Lookup(200));
ASSERT_EQ(102, Lookup(201));
ASSERT_EQ(103, Lookup(202));
ASSERT_EQ(104, Lookup(203));
EXPECT_EQ(101, Lookup(200));
EXPECT_EQ(102, Lookup(201));
EXPECT_EQ(103, Lookup(202));
EXPECT_EQ(104, Lookup(203));

// Cleaning up all the handles
cache_->Release(h201);
Expand All @@ -538,37 +507,22 @@ TEST_P(CacheTest, EvictionPolicyRef) {
}

TEST_P(CacheTest, EvictEmptyCache) {
auto type = GetParam();

// Insert item large than capacity to trigger eviction on empty cache.
auto cache = NewCache(1, 0, false);
if (type == kLRU) {
ASSERT_OK(cache->Insert("foo", nullptr, &kDumbHelper, 10));
} else {
ASSERT_OK(cache->Insert(EncodeKey(1000), nullptr, &kDumbHelper, 10));
}
ASSERT_OK(cache->Insert(EncodeKey(1000), nullptr, &kDumbHelper, 10));
}

TEST_P(CacheTest, EraseFromDeleter) {
auto type = GetParam();

// Have deleter which will erase item from cache, which will re-enter
// the cache at that point.
std::shared_ptr<Cache> cache = NewCache(10, 0, false);
std::string foo, bar;
const Cache::CacheItemHelper* erase_helper;
if (type == kLRU) {
foo = "foo";
bar = "bar";
erase_helper = &kEraseOnDeleteHelper1;
} else {
foo = EncodeKey(1234);
bar = EncodeKey(5678);
erase_helper = &kEraseOnDeleteHelper2;
}
std::string foo = EncodeKey(1234);
std::string bar = EncodeKey(5678);

std::function<void()> erase_fn = [&]() { cache->Erase(foo); };

ASSERT_OK(cache->Insert(foo, nullptr, &kDumbHelper, 1));
ASSERT_OK(cache->Insert(bar, cache.get(), erase_helper, 1));
ASSERT_OK(cache->Insert(bar, &erase_fn, &kInvokeOnDeleteHelper, 1));

cache->Erase(bar);
ASSERT_EQ(nullptr, cache->Lookup(foo));
Expand Down Expand Up @@ -676,10 +630,10 @@ using TypedHandle = SharedCache::TypedHandle;
} // namespace

TEST_P(CacheTest, SetCapacity) {
auto type = GetParam();
if (IsHyperClock()) {
// TODO: update test & code for limited supoort
ROCKSDB_GTEST_BYPASS(
"FastLRUCache and HyperClockCache don't support arbitrary capacity "
"HyperClockCache doesn't support arbitrary capacity "
"adjustments.");
return;
}
Expand Down
Loading

0 comments on commit 3055d4a

Please sign in to comment.