#include "rocksdb/system_clock.h"
#include "test_util/testharness.h"
#include "util/bloom_impl.h"
#include "util/coding.h"
#include "util/hash.h"
#include "util/ribbon_config.h"
#include "util/ribbon_impl.h"
#include "util/stop_watch.h"
#include "util/string_util.h"
#ifndef GFLAGS
uint32_t FLAGS_thoroughness = 5;
uint32_t FLAGS_max_add = 0;
uint32_t FLAGS_min_check = 4000;
uint32_t FLAGS_max_check = 100000;
bool FLAGS_verbose = false;
bool FLAGS_find_occ = false;
bool FLAGS_find_slot_occ = false;
double FLAGS_find_next_factor = 1.618;
uint32_t FLAGS_find_iters = 10000;
uint32_t FLAGS_find_min_slots = 128;
uint32_t FLAGS_find_max_slots = 1000000;
bool FLAGS_optimize_homog = false;
uint32_t FLAGS_optimize_homog_slots = 30000000;
uint32_t FLAGS_optimize_homog_check = 200000;
double FLAGS_optimize_homog_granularity = 0.002;
#else
#include "util/gflags_compat.h"
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
DEFINE_uint32(thoroughness, 5, "iterations per configuration");
DEFINE_uint32(max_add, 0,
"Add up to this number of entries to a single filter in "
"CompactnessAndBacktrackAndFpRate; 0 == reasonable default");
DEFINE_uint32(min_check, 4000,
"Minimum number of novel entries for testing FP rate");
DEFINE_uint32(max_check, 10000,
"Maximum number of novel entries for testing FP rate");
DEFINE_bool(verbose, false, "Print extra details");
DEFINE_bool(find_occ, false, "whether to run the FindOccupancy tool");
DEFINE_bool(find_slot_occ, false,
"whether to show individual slot occupancies with "
"FindOccupancy tool");
DEFINE_double(find_next_factor, 1.618,
"factor to next num_slots for FindOccupancy");
DEFINE_uint32(find_iters, 10000, "number of samples for FindOccupancy");
DEFINE_uint32(find_min_slots, 128, "number of slots for FindOccupancy");
DEFINE_uint32(find_max_slots, 1000000, "number of slots for FindOccupancy");
DEFINE_bool(optimize_homog, false,
"whether to run the OptimizeHomogAtScale tool");
DEFINE_uint32(optimize_homog_slots, 30000000,
"number of slots for OptimizeHomogAtScale");
DEFINE_uint32(optimize_homog_check, 200000,
"number of queries for checking FP rate in OptimizeHomogAtScale");
DEFINE_double(
optimize_homog_granularity, 0.002,
"overhead change between FP rate checking in OptimizeHomogAtScale");
#endif
template <typename TypesAndSettings>
class RibbonTypeParamTest : public ::testing::Test {};
class RibbonTest : public ::testing::Test {};
namespace {
struct StandardKeyGen {
StandardKeyGen(const std::string& prefix, uint64_t id)
: id_(id), str_(prefix) {
ROCKSDB_NAMESPACE::PutFixed64(&str_, 0);
}
StandardKeyGen& operator++() {
++id_;
return *this;
}
StandardKeyGen& operator+=(uint64_t i) {
id_ += i;
return *this;
}
const std::string& operator*() {
ROCKSDB_NAMESPACE::EncodeFixed64(&str_[str_.size() - 8],
id_ * uint64_t{0x1500000001});
return str_;
}
bool operator==(const StandardKeyGen& other) const {
return id_ == other.id_;
}
bool operator!=(const StandardKeyGen& other) const {
return id_ != other.id_;
}
uint64_t id_;
std::string str_;
};
struct SmallKeyGen {
SmallKeyGen(const std::string& prefix, uint64_t id) : id_(id) {
id_ += ROCKSDB_NAMESPACE::GetSliceHash64(prefix);
ROCKSDB_NAMESPACE::PutFixed64(&str_, id_);
}
SmallKeyGen& operator++() {
++id_;
return *this;
}
SmallKeyGen& operator+=(uint64_t i) {
id_ += i;
return *this;
}
const std::string& operator*() {
ROCKSDB_NAMESPACE::EncodeFixed64(&str_[str_.size() - 8], id_);
return str_;
}
bool operator==(const SmallKeyGen& other) const { return id_ == other.id_; }
bool operator!=(const SmallKeyGen& other) const { return id_ != other.id_; }
uint64_t id_;
std::string str_;
};
template <typename KeyGen>
struct Hash32KeyGenWrapper : public KeyGen {
Hash32KeyGenWrapper(const std::string& prefix, uint64_t id)
: KeyGen(prefix, id) {}
uint32_t operator*() {
auto& key = *static_cast<KeyGen&>(*this);
return ROCKSDB_NAMESPACE::GetSliceHash(key);
}
};
template <typename KeyGen>
struct Hash64KeyGenWrapper : public KeyGen {
Hash64KeyGenWrapper(const std::string& prefix, uint64_t id)
: KeyGen(prefix, id) {}
uint64_t operator*() {
auto& key = *static_cast<KeyGen&>(*this);
return ROCKSDB_NAMESPACE::GetSliceHash64(key);
}
};
using ROCKSDB_NAMESPACE::ribbon::ConstructionFailureChance;
const std::vector<ConstructionFailureChance> kFailureOnly50Pct = {
ROCKSDB_NAMESPACE::ribbon::kOneIn2};
const std::vector<ConstructionFailureChance> kFailureOnlyRare = {
ROCKSDB_NAMESPACE::ribbon::kOneIn1000};
const std::vector<ConstructionFailureChance> kFailureAll = {
ROCKSDB_NAMESPACE::ribbon::kOneIn2, ROCKSDB_NAMESPACE::ribbon::kOneIn20,
ROCKSDB_NAMESPACE::ribbon::kOneIn1000};
}
using ROCKSDB_NAMESPACE::ribbon::ExpectedCollisionFpRate;
using ROCKSDB_NAMESPACE::ribbon::StandardHasher;
using ROCKSDB_NAMESPACE::ribbon::StandardRehasherAdapter;
struct DefaultTypesAndSettings {
using CoeffRow = ROCKSDB_NAMESPACE::Unsigned128;
using ResultRow = uint8_t;
using Index = uint32_t;
using Hash = uint64_t;
using Seed = uint32_t;
using Key = ROCKSDB_NAMESPACE::Slice;
static constexpr bool kIsFilter = true;
static constexpr bool kHomogeneous = false;
static constexpr bool kFirstCoeffAlwaysOne = true;
static constexpr bool kUseSmash = false;
static constexpr bool kAllowZeroStarts = false;
static Hash HashFn(const Key& key, uint64_t raw_seed) {
return ROCKSDB_NAMESPACE::Hash64(key.data(), key.size(), raw_seed);
}
using KeyGen = StandardKeyGen;
static const std::vector<ConstructionFailureChance>& FailureChanceToTest() {
return kFailureAll;
}
};
using TypesAndSettings_Coeff128 = DefaultTypesAndSettings;
struct TypesAndSettings_Coeff128Smash : public DefaultTypesAndSettings {
static constexpr bool kUseSmash = true;
};
struct TypesAndSettings_Coeff64 : public DefaultTypesAndSettings {
using CoeffRow = uint64_t;
};
struct TypesAndSettings_Coeff64Smash : public TypesAndSettings_Coeff64 {
static constexpr bool kUseSmash = true;
};
struct TypesAndSettings_Coeff64Smash0 : public TypesAndSettings_Coeff64Smash {
static constexpr bool kFirstCoeffAlwaysOne = false;
};
struct TypesAndSettings_Coeff128_Homog : public DefaultTypesAndSettings {
static constexpr bool kHomogeneous = true;
using ResultRow = uint8_t;
static const std::vector<ConstructionFailureChance>& FailureChanceToTest() {
return kFailureOnlyRare;
}
};
struct TypesAndSettings_Coeff128Smash_Homog
: public TypesAndSettings_Coeff128_Homog {
static constexpr bool kUseSmash = true;
};
struct TypesAndSettings_Coeff64_Homog : public TypesAndSettings_Coeff128_Homog {
using CoeffRow = uint64_t;
};
struct TypesAndSettings_Coeff64Smash_Homog
: public TypesAndSettings_Coeff64_Homog {
static constexpr bool kUseSmash = true;
};
struct AbridgedTypesAndSettings : public DefaultTypesAndSettings {
static const std::vector<ConstructionFailureChance>& FailureChanceToTest() {
return kFailureOnly50Pct;
}
};
struct TypesAndSettings_Result16 : public AbridgedTypesAndSettings {
using ResultRow = uint16_t;
};
struct TypesAndSettings_Result32 : public AbridgedTypesAndSettings {
using ResultRow = uint32_t;
};
struct TypesAndSettings_IndexSizeT : public AbridgedTypesAndSettings {
using Index = size_t;
};
struct TypesAndSettings_Hash32 : public AbridgedTypesAndSettings {
using Hash = uint32_t;
static Hash HashFn(const Key& key, Hash raw_seed) {
return ROCKSDB_NAMESPACE::Hash(key.data(), key.size(), raw_seed);
}
};
struct TypesAndSettings_Hash32_Result16 : public AbridgedTypesAndSettings {
using ResultRow = uint16_t;
};
struct TypesAndSettings_KeyString : public AbridgedTypesAndSettings {
using Key = std::string;
};
struct TypesAndSettings_Seed8 : public AbridgedTypesAndSettings {
using Seed = uint8_t;
};
struct TypesAndSettings_NoAlwaysOne : public AbridgedTypesAndSettings {
static constexpr bool kFirstCoeffAlwaysOne = false;
};
struct TypesAndSettings_AllowZeroStarts : public AbridgedTypesAndSettings {
static constexpr bool kAllowZeroStarts = true;
};
struct TypesAndSettings_Seed64 : public AbridgedTypesAndSettings {
using Seed = uint64_t;
};
struct TypesAndSettings_Rehasher
: public StandardRehasherAdapter<AbridgedTypesAndSettings> {
using KeyGen = Hash64KeyGenWrapper<StandardKeyGen>;
};
struct TypesAndSettings_Rehasher_Result16 : public TypesAndSettings_Rehasher {
using ResultRow = uint16_t;
};
struct TypesAndSettings_Rehasher_Result32 : public TypesAndSettings_Rehasher {
using ResultRow = uint32_t;
};
struct TypesAndSettings_Rehasher_Seed64
: public StandardRehasherAdapter<TypesAndSettings_Seed64> {
using KeyGen = Hash64KeyGenWrapper<StandardKeyGen>;
};
struct TypesAndSettings_Rehasher32
: public StandardRehasherAdapter<TypesAndSettings_Hash32> {
using KeyGen = Hash32KeyGenWrapper<StandardKeyGen>;
};
struct TypesAndSettings_Rehasher32_Coeff64
: public TypesAndSettings_Rehasher32 {
using CoeffRow = uint64_t;
};
struct TypesAndSettings_SmallKeyGen : public AbridgedTypesAndSettings {
using KeyGen = SmallKeyGen;
};
struct TypesAndSettings_Hash32_SmallKeyGen : public TypesAndSettings_Hash32 {
using KeyGen = SmallKeyGen;
};
struct TypesAndSettings_Coeff32 : public DefaultTypesAndSettings {
using CoeffRow = uint32_t;
};
struct TypesAndSettings_Coeff32Smash : public TypesAndSettings_Coeff32 {
static constexpr bool kUseSmash = true;
};
struct TypesAndSettings_Coeff16 : public DefaultTypesAndSettings {
using CoeffRow = uint16_t;
};
struct TypesAndSettings_Coeff16Smash : public TypesAndSettings_Coeff16 {
static constexpr bool kUseSmash = true;
};
using TestTypesAndSettings = ::testing::Types<
TypesAndSettings_Coeff128, TypesAndSettings_Coeff128Smash,
TypesAndSettings_Coeff64, TypesAndSettings_Coeff64Smash,
TypesAndSettings_Coeff64Smash0, TypesAndSettings_Coeff128_Homog,
TypesAndSettings_Coeff128Smash_Homog, TypesAndSettings_Coeff64_Homog,
TypesAndSettings_Coeff64Smash_Homog, TypesAndSettings_Result16,
TypesAndSettings_Result32, TypesAndSettings_IndexSizeT,
TypesAndSettings_Hash32, TypesAndSettings_Hash32_Result16,
TypesAndSettings_KeyString, TypesAndSettings_Seed8,
TypesAndSettings_NoAlwaysOne, TypesAndSettings_AllowZeroStarts,
TypesAndSettings_Seed64, TypesAndSettings_Rehasher,
TypesAndSettings_Rehasher_Result16, TypesAndSettings_Rehasher_Result32,
TypesAndSettings_Rehasher_Seed64, TypesAndSettings_Rehasher32,
TypesAndSettings_Rehasher32_Coeff64, TypesAndSettings_SmallKeyGen,
TypesAndSettings_Hash32_SmallKeyGen, TypesAndSettings_Coeff32,
TypesAndSettings_Coeff32Smash, TypesAndSettings_Coeff16,
TypesAndSettings_Coeff16Smash>;
TYPED_TEST_CASE(RibbonTypeParamTest, TestTypesAndSettings);
namespace {
uint64_t PoissonUpperBound(double expected_count, double stddevs_allowed) {
return static_cast<uint64_t>(
expected_count + stddevs_allowed * std::sqrt(expected_count) + 1.0);
}
uint64_t PoissonLowerBound(double expected_count, double stddevs_allowed) {
return static_cast<uint64_t>(std::max(
0.0, expected_count - stddevs_allowed * std::sqrt(expected_count)));
}
uint64_t FrequentPoissonUpperBound(double expected_count) {
return PoissonUpperBound(expected_count, 5.0);
}
uint64_t FrequentPoissonLowerBound(double expected_count) {
return PoissonLowerBound(expected_count, 5.0);
}
uint64_t InfrequentPoissonUpperBound(double expected_count) {
return PoissonUpperBound(expected_count, 3.0);
}
uint64_t InfrequentPoissonLowerBound(double expected_count) {
return PoissonLowerBound(expected_count, 3.0);
}
}
TYPED_TEST(RibbonTypeParamTest, CompactnessAndBacktrackAndFpRate) {
IMPORT_RIBBON_TYPES_AND_SETTINGS(TypeParam);
IMPORT_RIBBON_IMPL_TYPES(TypeParam);
using KeyGen = typename TypeParam::KeyGen;
using ConfigHelper =
ROCKSDB_NAMESPACE::ribbon::BandingConfigHelper<TypeParam>;
if (sizeof(CoeffRow) < 8) {
ROCKSDB_GTEST_BYPASS("Not fully supported");
return;
}
const auto log2_thoroughness =
static_cast<uint32_t>(ROCKSDB_NAMESPACE::FloorLog2(FLAGS_thoroughness));
const double log_max_add = std::log(
FLAGS_max_add > 0 ? FLAGS_max_add
: static_cast<uint32_t>(kCoeffBits * kCoeffBits) *
std::max(FLAGS_thoroughness, uint32_t{32}));
const double log_min_add = std::log(0.66 * SimpleSoln::RoundUpNumSlots(1));
ASSERT_GT(log_max_add, log_min_add);
const double diff_log_add = log_max_add - log_min_add;
for (ConstructionFailureChance cs : TypeParam::FailureChanceToTest()) {
double expected_reseeds;
switch (cs) {
default:
assert(false);
FALLTHROUGH_INTENDED;
case ROCKSDB_NAMESPACE::ribbon::kOneIn2:
fprintf(stderr, "== Failure: 50 percent\n");
expected_reseeds = 1.0;
break;
case ROCKSDB_NAMESPACE::ribbon::kOneIn20:
fprintf(stderr, "== Failure: 95 percent\n");
expected_reseeds = 0.053;
break;
case ROCKSDB_NAMESPACE::ribbon::kOneIn1000:
fprintf(stderr, "== Failure: 1/1000\n");
expected_reseeds = 0.001;
break;
}
uint64_t total_reseeds = 0;
uint64_t total_singles = 0;
uint64_t total_single_failures = 0;
uint64_t total_batch = 0;
uint64_t total_batch_successes = 0;
uint64_t total_fp_count = 0;
uint64_t total_added = 0;
uint64_t total_expand_trials = 0;
uint64_t total_expand_failures = 0;
double total_expand_overhead = 0.0;
uint64_t soln_query_nanos = 0;
uint64_t soln_query_count = 0;
uint64_t bloom_query_nanos = 0;
uint64_t isoln_query_nanos = 0;
uint64_t isoln_query_count = 0;
ROCKSDB_NAMESPACE::Random32 rnd(FLAGS_thoroughness);
for (uint32_t i = 0; i < FLAGS_thoroughness; ++i) {
double log_add =
std::fmod(0.6180339887498948482 * diff_log_add * i, diff_log_add) +
log_min_add;
uint32_t num_to_add = static_cast<uint32_t>(std::exp(log_add));
bool test_interleaved = (i % 7) != 6;
Index num_slots = Index{0} - 1;
--num_to_add;
for (;;) {
Index next_num_slots = SimpleSoln::RoundUpNumSlots(
ConfigHelper::GetNumSlots(num_to_add + 1, cs));
if (test_interleaved) {
next_num_slots = InterleavedSoln::RoundUpNumSlots(next_num_slots);
EXPECT_EQ(next_num_slots,
InterleavedSoln::RoundUpNumSlots(next_num_slots));
}
EXPECT_EQ(next_num_slots, SimpleSoln::RoundUpNumSlots(next_num_slots));
if (next_num_slots > num_slots) {
break;
}
num_slots = next_num_slots;
++num_to_add;
}
assert(num_slots < Index{0} - 1);
total_added += num_to_add;
std::string prefix;
ROCKSDB_NAMESPACE::PutFixed32(&prefix, rnd.Next());
std::string added_str = prefix + "added";
KeyGen keys_begin(added_str, 0);
KeyGen keys_end(added_str, num_to_add);
KeyGen one_more(prefix + "more", 1);
KeyGen two_more(prefix + "more", 2);
uint32_t batch_size =
static_cast<uint32_t>(2.0 * std::sqrt(num_slots - num_to_add));
if (batch_size < 10U) {
batch_size = 0;
}
std::string batch_str = prefix + "batch";
KeyGen batch_begin(batch_str, 0);
KeyGen batch_end(batch_str, batch_size);
std::string not_str = prefix + "not";
KeyGen other_keys_begin(not_str, 0);
KeyGen other_keys_end(not_str, FLAGS_max_check);
double overhead_ratio = 1.0 * num_slots / num_to_add;
if (FLAGS_verbose) {
fprintf(stderr, "Adding(%s) %u / %u Overhead: %g Batch size: %u\n",
test_interleaved ? "i" : "s", (unsigned)num_to_add,
(unsigned)num_slots, overhead_ratio, (unsigned)batch_size);
}
uint32_t max_ibytes =
static_cast<uint32_t>(sizeof(ResultRow) * num_slots);
size_t ibytes;
if (i == 0) {
ibytes = 0;
} else if (i == 1) {
ibytes = max_ibytes;
} else {
ibytes =
std::max(rnd.Uniformish(max_ibytes), rnd.Uniformish(max_ibytes));
}
std::unique_ptr<char[]> idata(new char[ibytes]);
InterleavedSoln isoln(idata.get(), ibytes);
SimpleSoln soln;
Hasher hasher;
bool first_single;
bool second_single;
bool batch_success;
{
Banding banding;
ASSERT_TRUE(
banding.ResetAndFindSeedToSolve(num_slots, keys_begin, keys_end));
Index occupied_count = banding.GetOccupiedCount();
Index more_added = 0;
if (TypeParam::kHomogeneous || overhead_ratio < 1.01 ||
batch_size == 0) {
first_single = false;
second_single = false;
batch_success = false;
} else {
KeyGen other_keys_too_big_end = other_keys_begin;
other_keys_too_big_end += num_to_add;
banding.EnsureBacktrackSize(std::max(num_to_add, batch_size));
EXPECT_FALSE(banding.AddRangeOrRollBack(other_keys_begin,
other_keys_too_big_end));
EXPECT_EQ(occupied_count, banding.GetOccupiedCount());
first_single = banding.Add(*one_more);
second_single = banding.Add(*two_more);
more_added += (first_single ? 1 : 0) + (second_single ? 1 : 0);
total_singles += 2U;
total_single_failures += 2U - more_added;
batch_success = banding.AddRangeOrRollBack(batch_begin, batch_end);
++total_batch;
if (batch_success) {
more_added += batch_size;
++total_batch_successes;
}
EXPECT_LE(banding.GetOccupiedCount(), occupied_count + more_added);
}
ASSERT_TRUE(
banding.AddRange(keys_begin, KeyGen(added_str, num_to_add / 8)));
EXPECT_LE(banding.GetOccupiedCount(), occupied_count + more_added);
soln.BackSubstFrom(banding);
if (test_interleaved) {
isoln.BackSubstFrom(banding);
}
Seed reseeds = banding.GetOrdinalSeed();
total_reseeds += reseeds;
EXPECT_LE(reseeds, 8 + log2_thoroughness);
if (reseeds > log2_thoroughness + 1) {
fprintf(
stderr, "%s high reseeds at %u, %u/%u: %u\n",
reseeds > log2_thoroughness + 8 ? "ERROR Extremely" : "Somewhat",
static_cast<unsigned>(i), static_cast<unsigned>(num_to_add),
static_cast<unsigned>(num_slots), static_cast<unsigned>(reseeds));
}
if (reseeds > 0) {
++total_expand_trials;
Index expand_count = 0;
Index ex_slots = num_slots;
banding.SetOrdinalSeed(0);
for (;; ++expand_count) {
ASSERT_LE(expand_count, log2_thoroughness);
ex_slots += ex_slots / kCoeffBits;
if (test_interleaved) {
ex_slots = InterleavedSoln::RoundUpNumSlots(ex_slots);
}
banding.Reset(ex_slots);
bool success = banding.AddRange(keys_begin, keys_end);
if (success) {
break;
}
}
total_expand_failures += expand_count;
total_expand_overhead += 1.0 * (ex_slots - num_slots) / num_slots;
}
hasher.SetOrdinalSeed(reseeds);
}
KeyGen cur = keys_begin;
while (cur != keys_end) {
ASSERT_TRUE(soln.FilterQuery(*cur, hasher));
ASSERT_TRUE(!test_interleaved || isoln.FilterQuery(*cur, hasher));
++cur;
}
if (first_single) {
ASSERT_TRUE(soln.FilterQuery(*one_more, hasher));
ASSERT_TRUE(!test_interleaved || isoln.FilterQuery(*one_more, hasher));
}
if (second_single) {
ASSERT_TRUE(soln.FilterQuery(*two_more, hasher));
ASSERT_TRUE(!test_interleaved || isoln.FilterQuery(*two_more, hasher));
}
if (batch_success) {
cur = batch_begin;
while (cur != batch_end) {
ASSERT_TRUE(soln.FilterQuery(*cur, hasher));
ASSERT_TRUE(!test_interleaved || isoln.FilterQuery(*cur, hasher));
++cur;
}
}
Index fp_count = 0;
cur = other_keys_begin;
{
ROCKSDB_NAMESPACE::StopWatchNano timer(
ROCKSDB_NAMESPACE::SystemClock::Default().get(), true);
while (cur != other_keys_end) {
bool fp = soln.FilterQuery(*cur, hasher);
fp_count += fp ? 1 : 0;
++cur;
}
soln_query_nanos += timer.ElapsedNanos();
soln_query_count += FLAGS_max_check;
}
{
double expected_fp_count = soln.ExpectedFpRate() * FLAGS_max_check;
double correction =
FLAGS_max_check * ExpectedCollisionFpRate(hasher, num_to_add);
EXPECT_LE(fp_count,
FrequentPoissonUpperBound(expected_fp_count + correction));
EXPECT_GE(fp_count,
FrequentPoissonLowerBound(expected_fp_count + correction));
}
total_fp_count += fp_count;
if (test_interleaved) {
Index ifp_count = 0;
cur = other_keys_begin;
ROCKSDB_NAMESPACE::StopWatchNano timer(
ROCKSDB_NAMESPACE::SystemClock::Default().get(), true);
while (cur != other_keys_end) {
ifp_count += isoln.FilterQuery(*cur, hasher) ? 1 : 0;
++cur;
}
isoln_query_nanos += timer.ElapsedNanos();
isoln_query_count += FLAGS_max_check;
{
double expected_fp_count = isoln.ExpectedFpRate() * FLAGS_max_check;
double correction =
FLAGS_max_check * ExpectedCollisionFpRate(hasher, num_to_add);
EXPECT_LE(ifp_count,
FrequentPoissonUpperBound(expected_fp_count + correction));
EXPECT_GE(ifp_count, FrequentPoissonLowerBound(
0.95 * expected_fp_count + correction));
}
EXPECT_GE(ifp_count, fp_count);
}
if (ibytes >= 64) {
Index bfp_count = 0;
cur = other_keys_begin;
ROCKSDB_NAMESPACE::StopWatchNano timer(
ROCKSDB_NAMESPACE::SystemClock::Default().get(), true);
while (cur != other_keys_end) {
uint64_t h = hasher.GetHash(*cur);
uint32_t h1 = ROCKSDB_NAMESPACE::Lower32of64(h);
uint32_t h2 = sizeof(Hash) >= 8 ? ROCKSDB_NAMESPACE::Upper32of64(h)
: h1 * 0x9e3779b9;
bfp_count +=
ROCKSDB_NAMESPACE::FastLocalBloomImpl::HashMayMatch(
h1, h2, static_cast<uint32_t>(ibytes), 6, idata.get())
? 1
: 0;
++cur;
}
bloom_query_nanos += timer.ElapsedNanos();
ASSERT_LT(bfp_count, FLAGS_max_check);
}
}
fprintf(stderr,
"Simple outside query, hot, incl hashing, ns/key: %g\n",
1.0 * soln_query_nanos / soln_query_count);
fprintf(stderr,
"Interleaved outside query, hot, incl hashing, ns/key: %g\n",
1.0 * isoln_query_nanos / isoln_query_count);
fprintf(stderr,
"Bloom outside query, hot, incl hashing, ns/key: %g\n",
1.0 * bloom_query_nanos / soln_query_count);
if (TypeParam::kHomogeneous) {
EXPECT_EQ(total_reseeds, 0U);
} else {
double average_reseeds = 1.0 * total_reseeds / FLAGS_thoroughness;
fprintf(stderr, "Average re-seeds: %g\n", average_reseeds);
EXPECT_LE(total_reseeds,
InfrequentPoissonUpperBound(1.15 * expected_reseeds *
FLAGS_thoroughness));
EXPECT_GE(total_reseeds,
InfrequentPoissonLowerBound(0.75 * expected_reseeds *
FLAGS_thoroughness));
}
if (total_expand_trials > 0) {
double average_expand_failures =
1.0 * total_expand_failures / total_expand_trials;
fprintf(stderr, "Average expand failures, and overhead: %g, %g\n",
average_expand_failures,
total_expand_overhead / total_expand_trials);
EXPECT_LE(total_expand_failures,
InfrequentPoissonUpperBound(1.0 * total_expand_trials));
} else {
fprintf(stderr, "Average expand failures: N/A\n");
}
if (total_singles > 0) {
double single_failure_rate = 1.0 * total_single_failures / total_singles;
fprintf(stderr, "Add'l single, failure rate: %g\n", single_failure_rate);
double expected_single_failures = 1.0 * total_singles /
(sizeof(CoeffRow) == 16 ? 128
: TypeParam::kUseSmash ? 64
: 32);
EXPECT_LE(total_single_failures,
InfrequentPoissonUpperBound(expected_single_failures));
}
if (total_batch > 0) {
double expected_batch_successes = 1.0 * total_batch / 2;
uint64_t lower_bound =
InfrequentPoissonLowerBound(expected_batch_successes);
fprintf(stderr, "Add'l batch, success rate: %g (>= %g)\n",
1.0 * total_batch_successes / total_batch,
1.0 * lower_bound / total_batch);
EXPECT_GE(total_batch_successes, lower_bound);
}
{
uint64_t total_checked = uint64_t{FLAGS_max_check} * FLAGS_thoroughness;
double expected_total_fp_count =
total_checked * std::pow(0.5, 8U * sizeof(ResultRow));
double average_added = 1.0 * total_added / FLAGS_thoroughness;
expected_total_fp_count +=
total_checked * ExpectedCollisionFpRate(Hasher(), average_added);
uint64_t upper_bound =
InfrequentPoissonUpperBound(expected_total_fp_count);
uint64_t lower_bound =
InfrequentPoissonLowerBound(expected_total_fp_count);
fprintf(stderr, "Average FP rate: %g (~= %g, <= %g, >= %g)\n",
1.0 * total_fp_count / total_checked,
expected_total_fp_count / total_checked,
1.0 * upper_bound / total_checked,
1.0 * lower_bound / total_checked);
EXPECT_LE(total_fp_count, upper_bound);
EXPECT_GE(total_fp_count, lower_bound);
}
}
}
TYPED_TEST(RibbonTypeParamTest, Extremes) {
IMPORT_RIBBON_TYPES_AND_SETTINGS(TypeParam);
IMPORT_RIBBON_IMPL_TYPES(TypeParam);
using KeyGen = typename TypeParam::KeyGen;
size_t bytes = 128 * 1024;
std::unique_ptr<char[]> buf(new char[bytes]);
InterleavedSoln isoln(buf.get(), bytes);
SimpleSoln soln;
Hasher hasher;
Banding banding;
KeyGen begin_and_end("foo", 123);
ASSERT_TRUE(banding.ResetAndFindSeedToSolve(
kCoeffBits, begin_and_end, begin_and_end, 0,
0));
soln.BackSubstFrom(banding);
isoln.BackSubstFrom(banding);
ASSERT_EQ(isoln.GetUpperNumColumns(), 8U * sizeof(ResultRow));
ASSERT_EQ(isoln.GetUpperStartBlock(), 0U);
KeyGen other_keys_begin("not", 0);
KeyGen other_keys_end("not", FLAGS_max_check);
Index fp_count = 0;
KeyGen cur = other_keys_begin;
while (cur != other_keys_end) {
bool isoln_query_result = isoln.FilterQuery(*cur, hasher);
bool soln_query_result = soln.FilterQuery(*cur, hasher);
ASSERT_EQ(isoln_query_result, soln_query_result);
if (!TypeParam::kHomogeneous) {
ASSERT_EQ(soln_query_result, hasher.GetResultRowFromHash(
hasher.GetHash(*cur)) == ResultRow{0});
}
fp_count += soln_query_result ? 1 : 0;
++cur;
}
{
ASSERT_EQ(isoln.ExpectedFpRate(), soln.ExpectedFpRate());
double expected_fp_count = isoln.ExpectedFpRate() * FLAGS_max_check;
EXPECT_LE(fp_count, InfrequentPoissonUpperBound(expected_fp_count));
if (TypeParam::kHomogeneous) {
} else {
EXPECT_GE(fp_count, InfrequentPoissonLowerBound(expected_fp_count));
}
}
KeyGen key_begin("added", 0);
KeyGen key_end("added", 1);
ASSERT_TRUE(banding.ResetAndFindSeedToSolve(
kCoeffBits, key_begin, key_end, 0,
0));
InterleavedSoln isoln2(nullptr, 0);
isoln2.BackSubstFrom(banding);
ASSERT_EQ(isoln2.GetUpperNumColumns(), 0U);
ASSERT_EQ(isoln2.GetUpperStartBlock(), 0U);
ASSERT_TRUE(isoln2.FilterQuery(*other_keys_begin, hasher));
ASSERT_EQ(isoln2.ExpectedFpRate(), 1.0);
}
TEST(RibbonTest, AllowZeroStarts) {
IMPORT_RIBBON_TYPES_AND_SETTINGS(TypesAndSettings_AllowZeroStarts);
IMPORT_RIBBON_IMPL_TYPES(TypesAndSettings_AllowZeroStarts);
using KeyGen = StandardKeyGen;
InterleavedSoln isoln(nullptr, 0);
SimpleSoln soln;
Hasher hasher;
Banding banding;
KeyGen begin("foo", 0);
KeyGen end("foo", 1);
ASSERT_FALSE(banding.ResetAndFindSeedToSolve( 0, begin, end));
KeyGen begin_and_end("foo", 123);
ASSERT_TRUE(banding.ResetAndFindSeedToSolve( 0, begin_and_end,
begin_and_end));
Seed reseeds = banding.GetOrdinalSeed();
ASSERT_EQ(reseeds, 0U);
hasher.SetOrdinalSeed(reseeds);
isoln.BackSubstFrom(banding);
soln.BackSubstFrom(banding);
ASSERT_FALSE(isoln.FilterQuery(*begin, hasher));
ASSERT_FALSE(soln.FilterQuery(*begin, hasher));
ASSERT_EQ(isoln.ExpectedFpRate(), 0.0);
ASSERT_EQ(soln.ExpectedFpRate(), 0.0);
}
TEST(RibbonTest, RawAndOrdinalSeeds) {
StandardHasher<TypesAndSettings_Seed64> hasher64;
StandardHasher<DefaultTypesAndSettings> hasher64_32;
StandardHasher<TypesAndSettings_Hash32> hasher32;
StandardHasher<TypesAndSettings_Seed8> hasher8;
for (uint32_t limit : {0xffU, 0xffffU}) {
std::vector<bool> seen(limit + 1);
for (uint32_t i = 0; i < limit; ++i) {
hasher64.SetOrdinalSeed(i);
auto raw64 = hasher64.GetRawSeed();
hasher32.SetOrdinalSeed(i);
auto raw32 = hasher32.GetRawSeed();
hasher8.SetOrdinalSeed(static_cast<uint8_t>(i));
auto raw8 = hasher8.GetRawSeed();
{
hasher64_32.SetOrdinalSeed(i);
auto raw64_32 = hasher64_32.GetRawSeed();
ASSERT_EQ(raw64_32, raw32); }
if (i == 0) {
ASSERT_EQ(raw64, 0U);
ASSERT_EQ(raw32, 0U);
ASSERT_EQ(raw8, 0U);
} else {
ASSERT_GT(raw64, raw32);
ASSERT_GT(raw32, raw8);
}
ASSERT_EQ(static_cast<uint32_t>(raw64), raw32);
ASSERT_EQ(static_cast<uint8_t>(raw32), raw8);
uint32_t v = static_cast<uint32_t>(raw32 & limit);
ASSERT_EQ(raw64 & limit, v);
ASSERT_FALSE(seen[v]);
seen[v] = true;
}
}
}
namespace {
struct PhsfInputGen {
PhsfInputGen(const std::string& prefix, uint64_t id) : id_(id) {
val_.first = prefix;
ROCKSDB_NAMESPACE::PutFixed64(&val_.first, 0);
}
PhsfInputGen& operator++() {
++id_;
return *this;
}
const std::pair<std::string, uint8_t>& operator*() {
ROCKSDB_NAMESPACE::EncodeFixed64(&val_.first[val_.first.size() - 8],
id_ * uint64_t{0x1500000001});
val_.second = static_cast<uint8_t>(id_ * 7 / 8);
return val_;
}
const std::pair<std::string, uint8_t>* operator->() { return &**this; }
bool operator==(const PhsfInputGen& other) const {
return id_ == other.id_;
}
bool operator!=(const PhsfInputGen& other) const {
return id_ != other.id_;
}
uint64_t id_;
std::pair<std::string, uint8_t> val_;
};
struct PhsfTypesAndSettings : public DefaultTypesAndSettings {
static constexpr bool kIsFilter = false;
};
}
TEST(RibbonTest, PhsfBasic) {
IMPORT_RIBBON_TYPES_AND_SETTINGS(PhsfTypesAndSettings);
IMPORT_RIBBON_IMPL_TYPES(PhsfTypesAndSettings);
Index num_slots = 12800;
Index num_to_add = static_cast<Index>(num_slots / 1.02);
PhsfInputGen begin("in", 0);
PhsfInputGen end("in", num_to_add);
std::unique_ptr<char[]> idata(new char[ num_slots]);
InterleavedSoln isoln(idata.get(), num_slots);
SimpleSoln soln;
Hasher hasher;
{
Banding banding;
ASSERT_TRUE(banding.ResetAndFindSeedToSolve(num_slots, begin, end));
soln.BackSubstFrom(banding);
isoln.BackSubstFrom(banding);
hasher.SetOrdinalSeed(banding.GetOrdinalSeed());
}
for (PhsfInputGen cur = begin; cur != end; ++cur) {
ASSERT_EQ(cur->second, soln.PhsfQuery(cur->first, hasher));
ASSERT_EQ(cur->second, isoln.PhsfQuery(cur->first, hasher));
}
}
TYPED_TEST(RibbonTypeParamTest, FindOccupancy) {
IMPORT_RIBBON_TYPES_AND_SETTINGS(TypeParam);
IMPORT_RIBBON_IMPL_TYPES(TypeParam);
using KeyGen = typename TypeParam::KeyGen;
if (!FLAGS_find_occ) {
ROCKSDB_GTEST_BYPASS("Tool disabled during unit test runs");
return;
}
KeyGen cur(std::to_string(testing::UnitTest::GetInstance()->random_seed()),
0);
Banding banding;
Index num_slots = InterleavedSoln::RoundUpNumSlots(FLAGS_find_min_slots);
Index max_slots = InterleavedSoln::RoundUpNumSlots(FLAGS_find_max_slots);
while (num_slots <= max_slots) {
std::map<int32_t, uint32_t> rem_histogram;
std::map<Index, uint32_t> slot_histogram;
if (FLAGS_find_slot_occ) {
for (Index i = 0; i < kCoeffBits; ++i) {
slot_histogram[i] = 0;
slot_histogram[num_slots - 1 - i] = 0;
slot_histogram[num_slots / 2 - kCoeffBits / 2 + i] = 0;
}
}
uint64_t total_added = 0;
for (uint32_t i = 0; i < FLAGS_find_iters; ++i) {
banding.Reset(num_slots);
uint32_t j = 0;
KeyGen end = cur;
end += num_slots + num_slots / 10;
for (; cur != end; ++cur) {
if (banding.Add(*cur)) {
++j;
} else {
break;
}
}
total_added += j;
for (auto& slot : slot_histogram) {
slot.second += banding.IsOccupied(slot.first);
}
int32_t bucket =
static_cast<int32_t>(num_slots) - static_cast<int32_t>(j);
rem_histogram[bucket]++;
if (FLAGS_verbose) {
fprintf(stderr, "num_slots: %u i: %u / %u avg_overhead: %g\r",
static_cast<unsigned>(num_slots), static_cast<unsigned>(i),
static_cast<unsigned>(FLAGS_find_iters),
1.0 * (i + 1) * num_slots / total_added);
}
}
if (FLAGS_verbose) {
fprintf(stderr, "\n");
}
uint32_t cumulative = 0;
double p50_rem = 0;
double p95_rem = 0;
double p99_9_rem = 0;
for (auto& h : rem_histogram) {
double before = 1.0 * cumulative / FLAGS_find_iters;
double not_after = 1.0 * (cumulative + h.second) / FLAGS_find_iters;
if (FLAGS_verbose) {
fprintf(stderr, "overhead: %g before: %g not_after: %g\n",
1.0 * num_slots / (num_slots - h.first), before, not_after);
}
cumulative += h.second;
if (before < 0.5 && 0.5 <= not_after) {
double portion = (0.5 - before) / (not_after - before);
p50_rem = h.first + portion;
} else if (before < 0.95 && 0.95 <= not_after) {
double portion = (0.95 - before) / (not_after - before);
p95_rem = h.first + portion;
} else if (before < 0.999 && 0.999 <= not_after) {
double portion = (0.999 - before) / (not_after - before);
p99_9_rem = h.first + portion;
}
}
for (auto& slot : slot_histogram) {
fprintf(stderr, "slot[%u] occupied: %g\n", (unsigned)slot.first,
1.0 * slot.second / FLAGS_find_iters);
}
double mean_rem =
(1.0 * FLAGS_find_iters * num_slots - total_added) / FLAGS_find_iters;
fprintf(
stderr,
"num_slots: %u iters: %u mean_ovr: %g p50_ovr: %g p95_ovr: %g "
"p99.9_ovr: %g mean_rem: %g p50_rem: %g p95_rem: %g p99.9_rem: %g\n",
static_cast<unsigned>(num_slots),
static_cast<unsigned>(FLAGS_find_iters),
1.0 * num_slots / (num_slots - mean_rem),
1.0 * num_slots / (num_slots - p50_rem),
1.0 * num_slots / (num_slots - p95_rem),
1.0 * num_slots / (num_slots - p99_9_rem), mean_rem, p50_rem, p95_rem,
p99_9_rem);
num_slots = std::max(
num_slots + 1, static_cast<Index>(num_slots * FLAGS_find_next_factor));
num_slots = InterleavedSoln::RoundUpNumSlots(num_slots);
}
}
TYPED_TEST(RibbonTypeParamTest, OptimizeHomogAtScale) {
IMPORT_RIBBON_TYPES_AND_SETTINGS(TypeParam);
IMPORT_RIBBON_IMPL_TYPES(TypeParam);
using KeyGen = typename TypeParam::KeyGen;
if (!FLAGS_optimize_homog) {
ROCKSDB_GTEST_BYPASS("Tool disabled during unit test runs");
return;
}
if (!TypeParam::kHomogeneous) {
ROCKSDB_GTEST_BYPASS("Only for Homogeneous Ribbon");
return;
}
KeyGen cur(std::to_string(testing::UnitTest::GetInstance()->random_seed()),
0);
Banding banding;
Index num_slots = SimpleSoln::RoundUpNumSlots(FLAGS_optimize_homog_slots);
banding.Reset(num_slots);
double target_overhead = 1.20;
uint32_t num_added = 0;
do {
do {
(void)banding.Add(*cur);
++cur;
++num_added;
} while (1.0 * num_slots / num_added > target_overhead);
SimpleSoln soln;
soln.BackSubstFrom(banding);
std::array<uint32_t, 8U * sizeof(ResultRow)> fp_counts_by_cols;
fp_counts_by_cols.fill(0U);
for (uint32_t i = 0; i < FLAGS_optimize_homog_check; ++i) {
ResultRow r = soln.PhsfQuery(*cur, banding);
++cur;
for (size_t j = 0; j < fp_counts_by_cols.size(); ++j) {
if ((r & 1) == 1) {
break;
}
fp_counts_by_cols[j]++;
r /= 2;
}
}
fprintf(stderr, "band_ovr: %g ", 1.0 * num_slots / num_added);
for (unsigned j = 0; j < fp_counts_by_cols.size(); ++j) {
double inv_fp_rate =
1.0 * FLAGS_optimize_homog_check / fp_counts_by_cols[j];
double equiv_cols = std::log(inv_fp_rate) * 1.4426950409;
double actual_overhead =
1.0 * (j + 1) * num_slots / (equiv_cols * num_added);
fprintf(stderr, "ovr_%u: %g ", j + 1, actual_overhead);
}
fprintf(stderr, "\n");
target_overhead -= FLAGS_optimize_homog_granularity;
} while (target_overhead > 1.0);
}
int main(int argc, char** argv) {
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
#ifdef GFLAGS
ParseCommandLineFlags(&argc, &argv, true);
#endif return RUN_ALL_TESTS();
}