Refine Ribbon configuration, improve testing, add Homogeneous (#7879)

Summary:
This change only affects non-schema-critical aspects of the production candidate Ribbon filter. Specifically, it refines choice of internal configuration parameters based on inputs. The changes are minor enough that the schema tests in bloom_test, some of which depend on this, are unaffected. There are also some minor optimizations and refactorings.

This would be a schema change for "smash" Ribbon, to fix some known issues with small filters, but "smash" Ribbon is not accessible in public APIs. Unit test CompactnessAndBacktrackAndFpRate updated to test small and medium-large filters. Run with --thoroughness=100 or so for much better detection power (not appropriate for continuous regression testing).

Homogenous Ribbon:
This change adds internally a Ribbon filter variant we call Homogeneous Ribbon, in collaboration with Stefan Walzer. The expected "result" value for every key is zero, instead of computed from a hash. Entropy for queries not to be false positives comes from free variables ("overhead") in the solution structure, which are populated pseudorandomly. Construction is slightly faster for not tracking result values, and never fails. Instead, FP rate can jump up whenever and whereever entries are packed too tightly. For small structures, we can choose overhead to make this FP rate jump unlikely, as seen in updated unit test CompactnessAndBacktrackAndFpRate.

Unlike standard Ribbon, Homogeneous Ribbon seems to scale to arbitrary number of keys when accepting an FP rate penalty for small pockets of high FP rate in the structure. For example, 64-bit ribbon with 8 solution columns and 10% allocated space overhead for slots seems to achieve about 10.5% space overhead vs. information-theoretic minimum based on its observed FP rate with expected pockets of degradation. (FP rate is close to 1/256.) If targeting a higher FP rate with fewer solution columns, Homogeneous Ribbon can be even more space efficient, because the penalty from degradation is relatively smaller. If targeting a lower FP rate, Homogeneous Ribbon is less space efficient, as more allocated overhead is needed to keep the FP rate impact of degradation relatively under control. The new OptimizeHomogAtScale tool in ribbon_test helps to find these optimal allocation overheads for different numbers of solution columns. And Ribbon widths, with 128-bit Ribbon apparently cutting space overheads in half vs. 64-bit.

Other misc item specifics:
* Ribbon APIs in util/ribbon_config.h now provide configuration data for not just 5% construction failure rate (95% success), but also 50% and 0.1%.
  * Note that the Ribbon structure does not exhibit "threshold" behavior as standard Xor filter does, so there is a roughly fixed space penalty to cut construction failure rate in half. Thus, there isn't really an "almost sure" setting.
  * Although we can extrapolate settings for large filters, we don't have a good formula for configuring smaller filters (< 2^17 slots or so), and efforts to summarize with a formula have failed. Thus, small data is hard-coded from updated FindOccupancy tool.
* Enhances ApproximateNumEntries for public API Ribbon using more precise data (new API GetNumToAdd), thus a more accurate but not perfect reversal of CalculateSpace. (bloom_test updated to expect the greater precision)
* Move EndianSwapValue from coding.h to coding_lean.h to keep Ribbon code easily transferable from RocksDB
* Add some missing 'const' to member functions
* Small optimization to 128-bit BitParity
* Small refactoring of BandingStorage in ribbon_alg.h to support Homogeneous Ribbon
* CompactnessAndBacktrackAndFpRate now has an "expand" test: on construction failure, a possible alternative to re-seeding hash functions is simply to increase the number of slots (allocated space overhead) and try again with essentially the same hash values. (Start locations will be different roundings of the same scaled hash values--because fastrange not mod.) This seems to be as effective or more effective than re-seeding, as long as we increase the number of slots (m) by roughly m += m/w where w is the Ribbon width. This way, there is effectively an expansion by one slot for each ribbon-width window in the banding. (This approach assumes that getting "bad data" from your hash function is as unlikely as it naturally should be, e.g. no adversary.)
* 32-bit and 16-bit Ribbon configurations are added to ribbon_test for understanding their behavior, e.g. with FindOccupancy. They are not considered useful at this time and not tested with CompactnessAndBacktrackAndFpRate.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/7879

Test Plan: unit test updates included

Reviewed By: jay-zhuang

Differential Revision: D26371245

Pulled By: pdillinger

fbshipit-source-id: da6600d90a3785b99ad17a88b2a3027710b4ea3a
This commit is contained in:
Peter Dillinger 2021-02-26 08:48:55 -08:00 committed by Facebook GitHub Bot
parent c370d8aa12
commit a8b3b9a20c
13 changed files with 1604 additions and 499 deletions

View File

@ -766,6 +766,7 @@ set(SOURCES
util/murmurhash.cc util/murmurhash.cc
util/random.cc util/random.cc
util/rate_limiter.cc util/rate_limiter.cc
util/ribbon_config.cc
util/slice.cc util/slice.cc
util/file_checksum_helper.cc util/file_checksum_helper.cc
util/status.cc util/status.cc

View File

@ -342,6 +342,7 @@ cpp_library(
"util/murmurhash.cc", "util/murmurhash.cc",
"util/random.cc", "util/random.cc",
"util/rate_limiter.cc", "util/rate_limiter.cc",
"util/ribbon_config.cc",
"util/slice.cc", "util/slice.cc",
"util/status.cc", "util/status.cc",
"util/string_util.cc", "util/string_util.cc",
@ -647,6 +648,7 @@ cpp_library(
"util/murmurhash.cc", "util/murmurhash.cc",
"util/random.cc", "util/random.cc",
"util/rate_limiter.cc", "util/rate_limiter.cc",
"util/ribbon_config.cc",
"util/slice.cc", "util/slice.cc",
"util/status.cc", "util/status.cc",
"util/string_util.cc", "util/string_util.cc",

1
src.mk
View File

@ -208,6 +208,7 @@ LIB_SOURCES = \
util/murmurhash.cc \ util/murmurhash.cc \
util/random.cc \ util/random.cc \
util/rate_limiter.cc \ util/rate_limiter.cc \
util/ribbon_config.cc \
util/slice.cc \ util/slice.cc \
util/file_checksum_helper.cc \ util/file_checksum_helper.cc \
util/status.cc \ util/status.cc \

View File

@ -21,6 +21,7 @@
#include "util/bloom_impl.h" #include "util/bloom_impl.h"
#include "util/coding.h" #include "util/coding.h"
#include "util/hash.h" #include "util/hash.h"
#include "util/ribbon_config.h"
#include "util/ribbon_impl.h" #include "util/ribbon_impl.h"
namespace ROCKSDB_NAMESPACE { namespace ROCKSDB_NAMESPACE {
@ -399,6 +400,7 @@ struct Standard128RibbonRehasherTypesAndSettings {
// These are schema-critical. Any change almost certainly changes // These are schema-critical. Any change almost certainly changes
// underlying data. // underlying data.
static constexpr bool kIsFilter = true; static constexpr bool kIsFilter = true;
static constexpr bool kHomogeneous = false;
static constexpr bool kFirstCoeffAlwaysOne = true; static constexpr bool kFirstCoeffAlwaysOne = true;
static constexpr bool kUseSmash = false; static constexpr bool kUseSmash = false;
using CoeffRow = ROCKSDB_NAMESPACE::Unsigned128; using CoeffRow = ROCKSDB_NAMESPACE::Unsigned128;
@ -598,8 +600,7 @@ class Standard128RibbonBitsBuilder : public XXH3pFilterBitsBuilder {
// Let's not bother accounting for overflow to Bloom filter // Let's not bother accounting for overflow to Bloom filter
// (Includes NaN case) // (Includes NaN case)
if (!(max_slots < if (!(max_slots < ConfigHelper::GetNumSlots(kMaxRibbonEntries))) {
BandingType::GetNumSlotsFor95PctSuccess(kMaxRibbonEntries))) {
return kMaxRibbonEntries; return kMaxRibbonEntries;
} }
@ -628,12 +629,7 @@ class Standard128RibbonBitsBuilder : public XXH3pFilterBitsBuilder {
slots = SolnType::RoundDownNumSlots(slots - 1); slots = SolnType::RoundDownNumSlots(slots - 1);
} }
// Using slots instead of entries to get overhead factor estimate uint32_t num_entries = ConfigHelper::GetNumToAdd(slots);
double f = BandingType::GetFactorFor95PctSuccess(slots);
uint32_t num_entries = static_cast<uint32_t>(slots / f);
// Improve precision with another round
f = BandingType::GetFactorFor95PctSuccess(num_entries);
num_entries = static_cast<uint32_t>(slots / f + 0.999999999);
// Consider possible Bloom fallback for small filters // Consider possible Bloom fallback for small filters
if (slots < 1024) { if (slots < 1024) {
@ -675,9 +671,10 @@ class Standard128RibbonBitsBuilder : public XXH3pFilterBitsBuilder {
using TS = Standard128RibbonTypesAndSettings; using TS = Standard128RibbonTypesAndSettings;
using SolnType = ribbon::SerializableInterleavedSolution<TS>; using SolnType = ribbon::SerializableInterleavedSolution<TS>;
using BandingType = ribbon::StandardBanding<TS>; using BandingType = ribbon::StandardBanding<TS>;
using ConfigHelper = ribbon::BandingConfigHelper1TS<ribbon::kOneIn20, TS>;
static uint32_t NumEntriesToNumSlots(uint32_t num_entries) { static uint32_t NumEntriesToNumSlots(uint32_t num_entries) {
uint32_t num_slots1 = BandingType::GetNumSlotsFor95PctSuccess(num_entries); uint32_t num_slots1 = ConfigHelper::GetNumSlots(num_entries);
return SolnType::RoundUpNumSlots(num_slots1); return SolnType::RoundUpNumSlots(num_slots1);
} }

View File

@ -431,10 +431,10 @@ TEST_P(FullBloomTest, FilterSize) {
size_t n2 = bits_builder->ApproximateNumEntries(space); size_t n2 = bits_builder->ApproximateNumEntries(space);
EXPECT_GE(n2, n); EXPECT_GE(n2, n);
size_t space2 = bits_builder->CalculateSpace(n2); size_t space2 = bits_builder->CalculateSpace(n2);
if (n > 6000 && GetParam() == BloomFilterPolicy::kStandard128Ribbon) { if (n > 12000 && GetParam() == BloomFilterPolicy::kStandard128Ribbon) {
// TODO(peterd): better approximation? // TODO(peterd): better approximation?
EXPECT_GE(space2, space); EXPECT_GE(space2, space);
EXPECT_LE(space2 * 0.98 - 16.0, space * 1.0); EXPECT_LE(space2 * 0.998, space * 1.0);
} else { } else {
EXPECT_EQ(space2, space); EXPECT_EQ(space2, space);
} }

View File

@ -320,38 +320,6 @@ inline bool GetVarsignedint64(Slice* input, int64_t* value) {
} }
} }
// Swaps between big and little endian. Can be used to in combination
// with the little-endian encoding/decoding functions to encode/decode
// big endian.
template <typename T>
inline T EndianSwapValue(T v) {
static_assert(std::is_integral<T>::value, "non-integral type");
#ifdef _MSC_VER
if (sizeof(T) == 2) {
return static_cast<T>(_byteswap_ushort(static_cast<uint16_t>(v)));
} else if (sizeof(T) == 4) {
return static_cast<T>(_byteswap_ulong(static_cast<uint32_t>(v)));
} else if (sizeof(T) == 8) {
return static_cast<T>(_byteswap_uint64(static_cast<uint64_t>(v)));
}
#else
if (sizeof(T) == 2) {
return static_cast<T>(__builtin_bswap16(static_cast<uint16_t>(v)));
} else if (sizeof(T) == 4) {
return static_cast<T>(__builtin_bswap32(static_cast<uint32_t>(v)));
} else if (sizeof(T) == 8) {
return static_cast<T>(__builtin_bswap64(static_cast<uint64_t>(v)));
}
#endif
// Recognized by clang as bswap, but not by gcc :(
T ret_val = 0;
for (size_t i = 0; i < sizeof(T); ++i) {
ret_val |= ((v >> (8 * i)) & 0xff) << (8 * (sizeof(T) - 1 - i));
}
return ret_val;
}
inline bool GetLengthPrefixedSlice(Slice* input, Slice* result) { inline bool GetLengthPrefixedSlice(Slice* input, Slice* result) {
uint32_t len = 0; uint32_t len = 0;
if (GetVarint32(input, &len) && input->size() >= len) { if (GetVarint32(input, &len) && input->size() >= len) {

View File

@ -98,4 +98,36 @@ inline uint64_t DecodeFixed64(const char* ptr) {
} }
} }
// Swaps between big and little endian. Can be used to in combination
// with the little-endian encoding/decoding functions to encode/decode
// big endian.
template <typename T>
inline T EndianSwapValue(T v) {
static_assert(std::is_integral<T>::value, "non-integral type");
#ifdef _MSC_VER
if (sizeof(T) == 2) {
return static_cast<T>(_byteswap_ushort(static_cast<uint16_t>(v)));
} else if (sizeof(T) == 4) {
return static_cast<T>(_byteswap_ulong(static_cast<uint32_t>(v)));
} else if (sizeof(T) == 8) {
return static_cast<T>(_byteswap_uint64(static_cast<uint64_t>(v)));
}
#else
if (sizeof(T) == 2) {
return static_cast<T>(__builtin_bswap16(static_cast<uint16_t>(v)));
} else if (sizeof(T) == 4) {
return static_cast<T>(__builtin_bswap32(static_cast<uint32_t>(v)));
} else if (sizeof(T) == 8) {
return static_cast<T>(__builtin_bswap64(static_cast<uint64_t>(v)));
}
#endif
// Recognized by clang as bswap, but not by gcc :(
T ret_val = 0;
for (size_t i = 0; i < sizeof(T); ++i) {
ret_val |= ((v >> (8 * i)) & 0xff) << (8 * (sizeof(T) - 1 - i));
}
return ret_val;
}
} // namespace ROCKSDB_NAMESPACE } // namespace ROCKSDB_NAMESPACE

View File

@ -215,7 +215,7 @@ inline int BitsSetToOne(Unsigned128 v) {
template <> template <>
inline int BitParity(Unsigned128 v) { inline int BitParity(Unsigned128 v) {
return BitParity(Lower64of128(v)) ^ BitParity(Upper64of128(v)); return BitParity(Lower64of128(v) ^ Upper64of128(v));
} }
template <typename T> template <typename T>

View File

@ -8,6 +8,7 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include "rocksdb/rocksdb_namespace.h"
#include "util/math128.h" #include "util/math128.h"
namespace ROCKSDB_NAMESPACE { namespace ROCKSDB_NAMESPACE {
@ -501,12 +502,13 @@ namespace ribbon {
// // slot index i. // // slot index i.
// void Prefetch(Index i) const; // void Prefetch(Index i) const;
// //
// // Returns a pointer to CoeffRow for slot index i. // // Load or store CoeffRow and ResultRow for slot index i.
// CoeffRow* CoeffRowPtr(Index i); // // (Gaussian row operations involve both sides of the equation.)
// // // Bool `for_back_subst` indicates that customizing values for
// // Returns a pointer to ResultRow for slot index i. (Gaussian row // // unconstrained solution rows (cr == 0) is allowed.
// // operations involve both side of the equation.) // void LoadRow(Index i, CoeffRow *cr, ResultRow *rr, bool for_back_subst)
// ResultRow* ResultRowPtr(Index i); // const;
// void StoreRow(Index i, CoeffRow cr, ResultRow rr);
// //
// // Returns the number of columns that can start an r-sequence of // // Returns the number of columns that can start an r-sequence of
// // coefficients, which is the number of slots minus r (kCoeffBits) // // coefficients, which is the number of slots minus r (kCoeffBits)
@ -548,6 +550,7 @@ bool BandingAdd(BandingStorage *bs, typename BandingStorage::Index start,
typename BandingStorage::CoeffRow cr, BacktrackStorage *bts, typename BandingStorage::CoeffRow cr, BacktrackStorage *bts,
typename BandingStorage::Index *backtrack_pos) { typename BandingStorage::Index *backtrack_pos) {
using CoeffRow = typename BandingStorage::CoeffRow; using CoeffRow = typename BandingStorage::CoeffRow;
using ResultRow = typename BandingStorage::ResultRow;
using Index = typename BandingStorage::Index; using Index = typename BandingStorage::Index;
Index i = start; Index i = start;
@ -561,18 +564,19 @@ bool BandingAdd(BandingStorage *bs, typename BandingStorage::Index start,
for (;;) { for (;;) {
assert((cr & 1) == 1); assert((cr & 1) == 1);
CoeffRow other = *(bs->CoeffRowPtr(i)); CoeffRow cr_at_i;
if (other == 0) { ResultRow rr_at_i;
*(bs->CoeffRowPtr(i)) = cr; bs->LoadRow(i, &cr_at_i, &rr_at_i, /* for_back_subst */ false);
*(bs->ResultRowPtr(i)) = rr; if (cr_at_i == 0) {
bs->StoreRow(i, cr, rr);
bts->BacktrackPut(*backtrack_pos, i); bts->BacktrackPut(*backtrack_pos, i);
++*backtrack_pos; ++*backtrack_pos;
return true; return true;
} }
assert((other & 1) == 1); assert((cr_at_i & 1) == 1);
// Gaussian row reduction // Gaussian row reduction
cr ^= other; cr ^= cr_at_i;
rr ^= *(bs->ResultRowPtr(i)); rr ^= rr_at_i;
if (cr == 0) { if (cr == 0) {
// Inconsistency or (less likely) redundancy // Inconsistency or (less likely) redundancy
break; break;
@ -678,12 +682,11 @@ bool BandingAddRange(BandingStorage *bs, BacktrackStorage *bts,
while (backtrack_pos > 0) { while (backtrack_pos > 0) {
--backtrack_pos; --backtrack_pos;
Index i = bts->BacktrackGet(backtrack_pos); Index i = bts->BacktrackGet(backtrack_pos);
*(bs->CoeffRowPtr(i)) = 0; // Clearing the ResultRow is not strictly required, but is required
// Not strictly required, but is required for good FP rate on // for good FP rate on inputs that might have been backtracked out.
// inputs that might have been backtracked out. (We don't want // (We don't want anything we've backtracked on to leak into final
// anything we've backtracked on to leak into final result, as // result, as that might not be "harmless".)
// that might not be "harmless".) bs->StoreRow(i, 0, 0);
*(bs->ResultRowPtr(i)) = 0;
} }
} }
return false; return false;
@ -780,8 +783,9 @@ void SimpleBackSubst(SimpleSolutionStorage *sss, const BandingStorage &bs) {
for (Index i = num_slots; i > 0;) { for (Index i = num_slots; i > 0;) {
--i; --i;
CoeffRow cr = *const_cast<BandingStorage &>(bs).CoeffRowPtr(i); CoeffRow cr;
ResultRow rr = *const_cast<BandingStorage &>(bs).ResultRowPtr(i); ResultRow rr;
bs.LoadRow(i, &cr, &rr, /* for_back_subst */ true);
// solution row // solution row
ResultRow sr = 0; ResultRow sr = 0;
for (Index j = 0; j < kResultBits; ++j) { for (Index j = 0; j < kResultBits; ++j) {
@ -976,8 +980,9 @@ inline void BackSubstBlock(typename BandingStorage::CoeffRow *state,
for (Index i = start_slot + kCoeffBits; i > start_slot;) { for (Index i = start_slot + kCoeffBits; i > start_slot;) {
--i; --i;
CoeffRow cr = *const_cast<BandingStorage &>(bs).CoeffRowPtr(i); CoeffRow cr;
ResultRow rr = *const_cast<BandingStorage &>(bs).ResultRowPtr(i); ResultRow rr;
bs.LoadRow(i, &cr, &rr, /* for_back_subst */ true);
for (Index j = 0; j < num_columns; ++j) { for (Index j = 0; j < num_columns; ++j) {
// Compute next solution bit at row i, column j (see derivation below) // Compute next solution bit at row i, column j (see derivation below)
CoeffRow tmp = state[j] << 1; CoeffRow tmp = state[j] << 1;

506
util/ribbon_config.cc Normal file
View File

@ -0,0 +1,506 @@
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "util/ribbon_config.h"
namespace ROCKSDB_NAMESPACE {
namespace ribbon {
namespace detail {
// Each instantiation of this struct is sufficiently unique for configuration
// purposes, and is only instantiated for settings where we support the
// configuration API. An application might only reference one instantiation,
// meaning the rest could be pruned at link time.
template <ConstructionFailureChance kCfc, uint64_t kCoeffBits, bool kUseSmash>
struct BandingConfigHelperData {
static constexpr size_t kKnownSize = 18U;
// Because of complexity in the data, for smaller numbers of slots
// (powers of two up to 2^17), we record known numbers that can be added
// with kCfc chance of construction failure and settings in template
// parameters. Zero means "unsupported (too small) number of slots".
// (GetNumToAdd below will use interpolation for numbers of slots
// between powers of two; double rather than integer values here make
// that more accurate.)
static const std::array<double, kKnownSize> kKnownToAddByPow2;
// For sufficiently large number of slots, doubling the number of
// slots will increase the expected overhead (slots over number added)
// by approximately this constant.
// (This is roughly constant regardless of ConstructionFailureChance and
// smash setting.)
// (Would be a constant if we had partial template specialization for
// static const members.)
static inline double GetFactorPerPow2() {
if (kCoeffBits == 128U) {
return 0.0038;
} else {
assert(kCoeffBits == 64U);
return 0.0083;
}
}
// Overhead factor for 2^(kKnownSize-1) slots
// (Would be a constant if we had partial template specialization for
// static const members.)
static inline double GetFinalKnownFactor() {
return 1.0 * (uint32_t{1} << (kKnownSize - 1)) /
kKnownToAddByPow2[kKnownSize - 1];
}
// GetFinalKnownFactor() - (kKnownSize-1) * GetFactorPerPow2()
// (Would be a constant if we had partial template specialization for
// static const members.)
static inline double GetBaseFactor() {
return GetFinalKnownFactor() - (kKnownSize - 1) * GetFactorPerPow2();
}
// Get overhead factor (slots over number to add) for sufficiently large
// number of slots (by log base 2)
static inline double GetFactorForLarge(double log2_num_slots) {
return GetBaseFactor() + log2_num_slots * GetFactorPerPow2();
}
// For a given power of two number of slots (specified by whole number
// log base 2), implements GetNumToAdd for such limited case, returning
// double for better interpolation in GetNumToAdd and GetNumSlots.
static inline double GetNumToAddForPow2(uint32_t log2_num_slots) {
assert(log2_num_slots <= 32); // help clang-analyze
if (log2_num_slots < kKnownSize) {
return kKnownToAddByPow2[log2_num_slots];
} else {
return 1.0 * (uint64_t{1} << log2_num_slots) /
GetFactorForLarge(1.0 * log2_num_slots);
}
}
};
// Based on data from FindOccupancy in ribbon_test
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn2, 128U, false>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0,
0,
0, // unsupported
252.984,
506.109,
1013.71,
2029.47,
4060.43,
8115.63,
16202.2,
32305.1,
64383.5,
128274,
}};
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn2, 128U, /*smash*/ true>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0,
0, // unsupported
126.274,
254.279,
510.27,
1022.24,
2046.02,
4091.99,
8154.98,
16244.3,
32349.7,
64426.6,
128307,
}};
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn2, 64U, false>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0,
0, // unsupported
124.94,
249.968,
501.234,
1004.06,
2006.15,
3997.89,
7946.99,
15778.4,
31306.9,
62115.3,
123284,
}};
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn2, 64U, /*smash*/ true>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0, // unsupported
62.2683,
126.259,
254.268,
509.975,
1019.98,
2026.16,
4019.75,
7969.8,
15798.2,
31330.3,
62134.2,
123255,
}};
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn20, 128U, false>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0,
0,
0, // unsupported
248.851,
499.532,
1001.26,
2003.97,
4005.59,
8000.39,
15966.6,
31828.1,
63447.3,
126506,
}};
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn20, 128U, /*smash*/ true>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0,
0, // unsupported
122.637,
250.651,
506.625,
1018.54,
2036.43,
4041.6,
8039.25,
16005,
31869.6,
63492.8,
126537,
}};
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn20, 64U, false>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0,
0, // unsupported
120.659,
243.346,
488.168,
976.373,
1948.86,
3875.85,
7704.97,
15312.4,
30395.1,
60321.8,
119813,
}};
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn20, 64U, /*smash*/ true>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0, // unsupported
58.6016,
122.619,
250.641,
503.595,
994.165,
1967.36,
3898.17,
7727.21,
15331.5,
30405.8,
60376.2,
119836,
}};
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn1000, 128U, false>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0,
0,
0, // unsupported
242.61,
491.887,
983.603,
1968.21,
3926.98,
7833.99,
15629,
31199.9,
62307.8,
123870,
}};
template <>
const std::array<double, 18> BandingConfigHelperData<
kOneIn1000, 128U, /*smash*/ true>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0,
0, // unsupported
117.19,
245.105,
500.748,
1010.67,
1993.4,
3950.01,
7863.31,
15652,
31262.1,
62462.8,
124095,
}};
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn1000, 64U, false>::kKnownToAddByPow2{{
0,
0,
0,
0,
0,
0,
0, // unsupported
114,
234.8,
471.498,
940.165,
1874,
3721.5,
7387.5,
14592,
29160,
57745,
115082,
}};
template <>
const std::array<double, 18>
BandingConfigHelperData<kOneIn1000, 64U, /*smash*/ true>::kKnownToAddByPow2{
{
0,
0,
0,
0,
0,
0, // unsupported
53.0434,
117,
245.312,
483.571,
950.251,
1878,
3736.34,
7387.97,
14618,
29142.9,
57838.8,
114932,
}};
// We hide these implementation details from the .h file with explicit
// instantiations below these partial specializations.
template <ConstructionFailureChance kCfc, uint64_t kCoeffBits, bool kUseSmash,
bool kHomogeneous>
uint32_t BandingConfigHelper1MaybeSupported<
kCfc, kCoeffBits, kUseSmash, kHomogeneous,
true /* kIsSupported */>::GetNumToAdd(uint32_t num_slots) {
using Data = detail::BandingConfigHelperData<kCfc, kCoeffBits, kUseSmash>;
if (num_slots == 0) {
return 0;
}
uint32_t num_to_add;
double log2_num_slots = std::log(num_slots) * 1.4426950409;
uint32_t floor_log2 = static_cast<uint32_t>(log2_num_slots);
if (floor_log2 + 1 < Data::kKnownSize) {
double ceil_portion = 1.0 * num_slots / (uint32_t{1} << floor_log2) - 1.0;
// Must be a supported number of slots
assert(Data::kKnownToAddByPow2[floor_log2] > 0.0);
// Weighted average of two nearest known data points
num_to_add = static_cast<uint32_t>(
ceil_portion * Data::kKnownToAddByPow2[floor_log2 + 1] +
(1.0 - ceil_portion) * Data::kKnownToAddByPow2[floor_log2]);
} else {
// Use formula for large values
double factor = Data::GetFactorForLarge(log2_num_slots);
assert(factor >= 1.0);
num_to_add = static_cast<uint32_t>(num_slots / factor);
}
if (kHomogeneous) {
// Even when standard filter construction would succeed, we might
// have loaded things up too much for Homogeneous filter. (Complete
// explanation not known but observed empirically.) This seems to
// correct for that, mostly affecting small filter configurations.
if (num_to_add >= 8) {
num_to_add -= 8;
} else {
assert(false);
}
}
return num_to_add;
}
template <ConstructionFailureChance kCfc, uint64_t kCoeffBits, bool kUseSmash,
bool kHomogeneous>
uint32_t BandingConfigHelper1MaybeSupported<
kCfc, kCoeffBits, kUseSmash, kHomogeneous,
true /* kIsSupported */>::GetNumSlots(uint32_t num_to_add) {
using Data = detail::BandingConfigHelperData<kCfc, kCoeffBits, kUseSmash>;
if (num_to_add == 0) {
return 0;
}
if (kHomogeneous) {
// Reverse of above in GetNumToAdd
num_to_add += 8;
}
double log2_num_to_add = std::log(num_to_add) * 1.4426950409;
uint32_t approx_log2_slots = static_cast<uint32_t>(log2_num_to_add + 0.5);
assert(approx_log2_slots <= 32); // help clang-analyze
double lower_num_to_add = Data::GetNumToAddForPow2(approx_log2_slots);
double upper_num_to_add;
if (approx_log2_slots == 0 || lower_num_to_add == /* unsupported */ 0) {
// Return minimum non-zero slots in standard implementation
return kUseSmash ? kCoeffBits : 2 * kCoeffBits;
} else if (num_to_add < lower_num_to_add) {
upper_num_to_add = lower_num_to_add;
--approx_log2_slots;
lower_num_to_add = Data::GetNumToAddForPow2(approx_log2_slots);
} else {
upper_num_to_add = Data::GetNumToAddForPow2(approx_log2_slots + 1);
}
assert(num_to_add >= lower_num_to_add);
assert(num_to_add < upper_num_to_add);
double upper_portion =
(num_to_add - lower_num_to_add) / (upper_num_to_add - lower_num_to_add);
double lower_num_slots = 1.0 * (uint64_t{1} << approx_log2_slots);
// Interpolation, round up
return static_cast<uint32_t>(upper_portion * lower_num_slots +
lower_num_slots + 0.999999999);
}
// These explicit instantiations enable us to hide most of the
// implementation details from the .h file. (The .h file currently
// needs to determine whether settings are "supported" or not.)
template struct BandingConfigHelper1MaybeSupported<kOneIn2, 128U, /*sm*/ false,
/*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn2, 128U, /*sm*/ true,
/*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn2, 128U, /*sm*/ false,
/*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn2, 128U, /*sm*/ true,
/*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn2, 64U, /*sm*/ false,
/*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn2, 64U, /*sm*/ true,
/*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn2, 64U, /*sm*/ false,
/*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn2, 64U, /*sm*/ true,
/*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn20, 128U, /*sm*/ false,
/*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn20, 128U, /*sm*/ true,
/*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn20, 128U, /*sm*/ false,
/*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn20, 128U, /*sm*/ true,
/*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn20, 64U, /*sm*/ false,
/*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn20, 64U, /*sm*/ true,
/*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn20, 64U, /*sm*/ false,
/*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn20, 64U, /*sm*/ true,
/*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<
kOneIn1000, 128U, /*sm*/ false, /*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<
kOneIn1000, 128U, /*sm*/ true, /*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<
kOneIn1000, 128U, /*sm*/ false, /*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<
kOneIn1000, 128U, /*sm*/ true, /*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<
kOneIn1000, 64U, /*sm*/ false, /*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn1000, 64U, /*sm*/ true,
/*hm*/ false, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<
kOneIn1000, 64U, /*sm*/ false, /*hm*/ true, /*sup*/ true>;
template struct BandingConfigHelper1MaybeSupported<kOneIn1000, 64U, /*sm*/ true,
/*hm*/ true, /*sup*/ true>;
} // namespace detail
} // namespace ribbon
} // namespace ROCKSDB_NAMESPACE

182
util/ribbon_config.h Normal file
View File

@ -0,0 +1,182 @@
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#pragma once
#include <array>
#include <cassert>
#include <cmath>
#include <cstdint>
#include "port/lang.h" // for FALLTHROUGH_INTENDED
#include "rocksdb/rocksdb_namespace.h"
namespace ROCKSDB_NAMESPACE {
namespace ribbon {
// RIBBON PHSF & RIBBON Filter (Rapid Incremental Boolean Banding ON-the-fly)
//
// ribbon_config.h: APIs for relating numbers of slots with numbers of
// additions for tolerable construction failure probabilities. This is
// separate from ribbon_impl.h because it might not be needed for
// some applications.
//
// This API assumes uint32_t for number of slots, as a single Ribbon
// linear system should not normally overflow that without big penalties.
//
// Template parameter kCoeffBits uses uint64_t for convenience in case it
// comes from size_t.
//
// Most of the complexity here is trying to optimize speed and
// compiled code size, using templates to minimize table look-ups and
// the compiled size of all linked look-up tables. Look-up tables are
// required because we don't have good formulas, and the data comes
// from running FindOccupancy in ribbon_test.
// Represents a chosen chance of successful Ribbon construction for a single
// seed. Allowing higher chance of failed construction can reduce space
// overhead but takes extra time in construction.
enum ConstructionFailureChance {
kOneIn2,
kOneIn20,
// When using kHomogeneous==true, construction failure chance should
// not generally exceed target FP rate, so it unlikely useful to
// allow a higher "failure" chance. In some cases, even more overhead
// is appropriate. (TODO)
kOneIn1000,
};
namespace detail {
// It is useful to compile ribbon_test linking to BandingConfigHelper with
// settings for which we do not have configuration data, as long as we don't
// run the code. This template hack supports that.
template <ConstructionFailureChance kCfc, uint64_t kCoeffBits, bool kUseSmash,
bool kHomogeneous, bool kIsSupported>
struct BandingConfigHelper1MaybeSupported {
public:
static uint32_t GetNumToAdd(uint32_t num_slots) {
// Unsupported
assert(num_slots == 0);
(void)num_slots;
return 0;
}
static uint32_t GetNumSlots(uint32_t num_to_add) {
// Unsupported
assert(num_to_add == 0);
(void)num_to_add;
return 0;
}
};
// Base class for BandingConfigHelper1 and helper for BandingConfigHelper
// with core implementations built on above data
template <ConstructionFailureChance kCfc, uint64_t kCoeffBits, bool kUseSmash,
bool kHomogeneous>
struct BandingConfigHelper1MaybeSupported<
kCfc, kCoeffBits, kUseSmash, kHomogeneous, true /* kIsSupported */> {
public:
// See BandingConfigHelper1. Implementation in ribbon_config.cc
static uint32_t GetNumToAdd(uint32_t num_slots);
// See BandingConfigHelper1. Implementation in ribbon_config.cc
static uint32_t GetNumSlots(uint32_t num_to_add);
};
} // namespace detail
template <ConstructionFailureChance kCfc, uint64_t kCoeffBits, bool kUseSmash,
bool kHomogeneous>
struct BandingConfigHelper1
: public detail::BandingConfigHelper1MaybeSupported<
kCfc, kCoeffBits, kUseSmash, kHomogeneous,
/* kIsSupported */ kCoeffBits == 64 || kCoeffBits == 128> {
public:
// Returns a number of entries that can be added to a given number of
// slots, with roughly kCfc chance of construction failure per seed,
// or better. Does NOT do rounding for InterleavedSoln; call
// RoundUpNumSlots for that.
//
// inherited:
// static uint32_t GetNumToAdd(uint32_t num_slots);
// Returns a number of slots for a given number of entries to add
// that should have roughly kCfc chance of construction failure per
// seed, or better. Does NOT do rounding for InterleavedSoln; call
// RoundUpNumSlots for that.
//
// num_to_add should not exceed roughly 2/3rds of the maximum value
// of the uint32_t type to avoid overflow.
//
// inherited:
// static uint32_t GetNumSlots(uint32_t num_to_add);
};
// Configured using TypesAndSettings as in ribbon_impl.h
template <ConstructionFailureChance kCfc, class TypesAndSettings>
struct BandingConfigHelper1TS
: public BandingConfigHelper1<
kCfc,
/* kCoeffBits */ sizeof(typename TypesAndSettings::CoeffRow) * 8U,
TypesAndSettings::kUseSmash, TypesAndSettings::kHomogeneous> {};
// Like BandingConfigHelper1TS except failure chance can be a runtime rather
// than compile time value.
template <class TypesAndSettings>
struct BandingConfigHelper {
public:
static constexpr ConstructionFailureChance kDefaultFailureChance =
TypesAndSettings::kHomogeneous ? kOneIn1000 : kOneIn20;
static uint32_t GetNumToAdd(
uint32_t num_slots,
ConstructionFailureChance max_failure = kDefaultFailureChance) {
switch (max_failure) {
default:
assert(false);
FALLTHROUGH_INTENDED;
case kOneIn20: {
using H1 = BandingConfigHelper1TS<kOneIn20, TypesAndSettings>;
return H1::GetNumToAdd(num_slots);
}
case kOneIn2: {
using H1 = BandingConfigHelper1TS<kOneIn2, TypesAndSettings>;
return H1::GetNumToAdd(num_slots);
}
case kOneIn1000: {
using H1 = BandingConfigHelper1TS<kOneIn1000, TypesAndSettings>;
return H1::GetNumToAdd(num_slots);
}
}
}
static uint32_t GetNumSlots(
uint32_t num_to_add,
ConstructionFailureChance max_failure = kDefaultFailureChance) {
switch (max_failure) {
default:
assert(false);
FALLTHROUGH_INTENDED;
case kOneIn20: {
using H1 = BandingConfigHelper1TS<kOneIn20, TypesAndSettings>;
return H1::GetNumSlots(num_to_add);
}
case kOneIn2: {
using H1 = BandingConfigHelper1TS<kOneIn2, TypesAndSettings>;
return H1::GetNumSlots(num_to_add);
}
case kOneIn1000: {
using H1 = BandingConfigHelper1TS<kOneIn1000, TypesAndSettings>;
return H1::GetNumSlots(num_to_add);
}
}
}
};
} // namespace ribbon
} // namespace ROCKSDB_NAMESPACE

View File

@ -8,6 +8,7 @@
#include <cmath> #include <cmath>
#include "port/port.h" // for PREFETCH #include "port/port.h" // for PREFETCH
#include "util/fastrange.h"
#include "util/ribbon_alg.h" #include "util/ribbon_alg.h"
namespace ROCKSDB_NAMESPACE { namespace ROCKSDB_NAMESPACE {
@ -23,6 +24,8 @@ namespace ribbon {
// and core design details. // and core design details.
// //
// TODO: more details on trade-offs and practical issues. // TODO: more details on trade-offs and practical issues.
//
// APIs for configuring Ribbon are in ribbon_config.h
// Ribbon implementations in this file take these parameters, which must be // Ribbon implementations in this file take these parameters, which must be
// provided in a class/struct type with members expressed in this concept: // provided in a class/struct type with members expressed in this concept:
@ -49,10 +52,22 @@ namespace ribbon {
// // construction. // // construction.
// static constexpr bool kIsFilter; // static constexpr bool kIsFilter;
// //
// // When true, enables a special "homogeneous" filter implementation that
// // is slightly faster to construct, and never fails to construct though
// // FP rate can quickly explode in cases where corresponding
// // non-homogeneous filter would fail (or nearly fail?) to construct.
// // For smaller filters, you can configure with ConstructionFailureChance
// // smaller than desired FP rate to largely counteract this effect.
// // TODO: configuring Homogeneous Ribbon for arbitrarily large filters
// // based on data from OptimizeHomogAtScale
// static constexpr bool kHomogeneous;
//
// // When true, adds a tiny bit more hashing logic on queries and // // When true, adds a tiny bit more hashing logic on queries and
// // construction to improve utilization at the beginning and end of // // construction to improve utilization at the beginning and end of
// // the structure. Recommended when CoeffRow is only 64 bits (or // // the structure. Recommended when CoeffRow is only 64 bits (or
// // less), so typical num_starts < 10k. // // less), so typical num_starts < 10k. Although this is compatible
// // with kHomogeneous, the competing space vs. time priorities might
// // not be useful.
// static constexpr bool kUseSmash; // static constexpr bool kUseSmash;
// //
// // When true, allows number of "starts" to be zero, for best support // // When true, allows number of "starts" to be zero, for best support
@ -201,7 +216,27 @@ class StandardHasher {
// This is not so much "critical path" code because it can be done in // This is not so much "critical path" code because it can be done in
// parallel (instruction level) with memory lookup. // parallel (instruction level) with memory lookup.
// //
// We do not need exhaustive remixing for CoeffRow, but just enough that // When we might have many entries squeezed into a single start,
// we need reasonably good remixing for CoeffRow.
if (TypesAndSettings::kUseSmash) {
// Reasonably good, reasonably fast, reasonably general.
// Probably not 1:1 but probably close enough.
Unsigned128 a = Multiply64to128(h, kAltCoeffFactor1);
Unsigned128 b = Multiply64to128(h, kAltCoeffFactor2);
auto cr = static_cast<CoeffRow>(b ^ (a << 64) ^ (a >> 64));
// Now ensure the value is non-zero
if (kFirstCoeffAlwaysOne) {
cr |= 1;
} else {
// Still have to ensure some bit is non-zero
cr |= (cr == 0) ? 1 : 0;
}
return cr;
}
// If not kUseSmash, we ensure we're not squeezing many entries into a
// single start, in part by ensuring num_starts > num_slots / 2. Thus,
// here we do not need good remixing for CoeffRow, but just enough that
// (a) every bit is reasonably independent from Start. // (a) every bit is reasonably independent from Start.
// (b) every Hash-length bit subsequence of the CoeffRow has full or // (b) every Hash-length bit subsequence of the CoeffRow has full or
// nearly full entropy from h. // nearly full entropy from h.
@ -220,25 +255,27 @@ class StandardHasher {
// even with a (likely) different multiplier here. // even with a (likely) different multiplier here.
Hash a = h * kCoeffAndResultFactor; Hash a = h * kCoeffAndResultFactor;
// If that's big enough, we're done. If not, we have to expand it,
// maybe up to 4x size.
uint64_t b = a;
static_assert( static_assert(
sizeof(Hash) == sizeof(uint64_t) || sizeof(Hash) == sizeof(uint32_t), sizeof(Hash) == sizeof(uint64_t) || sizeof(Hash) == sizeof(uint32_t),
"Supported sizes"); "Supported sizes");
// If that's big enough, we're done. If not, we have to expand it,
// maybe up to 4x size.
uint64_t b;
if (sizeof(Hash) < sizeof(uint64_t)) { if (sizeof(Hash) < sizeof(uint64_t)) {
// Almost-trivial hash expansion (OK - see above), favoring roughly // Almost-trivial hash expansion (OK - see above), favoring roughly
// equal number of 1's and 0's in result // equal number of 1's and 0's in result
b = (b << 32) ^ b ^ kCoeffXor32; b = (uint64_t{a} << 32) ^ (a ^ kCoeffXor32);
} else {
b = a;
} }
Unsigned128 c = b; static_assert(sizeof(CoeffRow) <= sizeof(Unsigned128), "Supported sizes");
static_assert(sizeof(CoeffRow) == sizeof(uint64_t) || Unsigned128 c;
sizeof(CoeffRow) == sizeof(Unsigned128),
"Supported sizes");
if (sizeof(uint64_t) < sizeof(CoeffRow)) { if (sizeof(uint64_t) < sizeof(CoeffRow)) {
// Almost-trivial hash expansion (OK - see above), favoring roughly // Almost-trivial hash expansion (OK - see above), favoring roughly
// equal number of 1's and 0's in result // equal number of 1's and 0's in result
c = (c << 64) ^ c ^ kCoeffXor64; c = (Unsigned128{b} << 64) ^ (b ^ kCoeffXor64);
} else {
c = b;
} }
auto cr = static_cast<CoeffRow>(c); auto cr = static_cast<CoeffRow>(c);
@ -261,7 +298,7 @@ class StandardHasher {
return static_cast<ResultRow>(~ResultRow{0}); return static_cast<ResultRow>(~ResultRow{0});
} }
inline ResultRow GetResultRowFromHash(Hash h) const { inline ResultRow GetResultRowFromHash(Hash h) const {
if (TypesAndSettings::kIsFilter) { if (TypesAndSettings::kIsFilter && !TypesAndSettings::kHomogeneous) {
// This is not so much "critical path" code because it can be done in // This is not so much "critical path" code because it can be done in
// parallel (instruction level) with memory lookup. // parallel (instruction level) with memory lookup.
// //
@ -272,10 +309,9 @@ class StandardHasher {
// the same bits computed for CoeffRow, which are reasonably // the same bits computed for CoeffRow, which are reasonably
// independent from Start. (Inlining and common subexpression // independent from Start. (Inlining and common subexpression
// elimination with GetCoeffRow should make this // elimination with GetCoeffRow should make this
// a single shared multiplication in generated code.) // a single shared multiplication in generated code when !kUseSmash.)
//
// TODO: fix & test the kUseSmash case with very small num_starts
Hash a = h * kCoeffAndResultFactor; Hash a = h * kCoeffAndResultFactor;
// The bits here that are *most* independent of Start are the highest // The bits here that are *most* independent of Start are the highest
// order bits (as in Knuth multiplicative hash). To make those the // order bits (as in Knuth multiplicative hash). To make those the
// most preferred for use in the result row, we do a bswap here. // most preferred for use in the result row, we do a bswap here.
@ -337,6 +373,8 @@ class StandardHasher {
// large random prime // large random prime
static constexpr Hash kCoeffAndResultFactor = static constexpr Hash kCoeffAndResultFactor =
static_cast<Hash>(0xc28f82822b650bedULL); static_cast<Hash>(0xc28f82822b650bedULL);
static constexpr uint64_t kAltCoeffFactor1 = 0x876f170be4f1fcb9U;
static constexpr uint64_t kAltCoeffFactor2 = 0xf0433a4aecda4c5fU;
// random-ish data // random-ish data
static constexpr uint32_t kCoeffXor32 = 0xa6293635U; static constexpr uint32_t kCoeffXor32 = 0xa6293635U;
static constexpr uint64_t kCoeffXor64 = 0xc367844a6e52731dU; static constexpr uint64_t kCoeffXor64 = 0xc367844a6e52731dU;
@ -447,15 +485,20 @@ class StandardBanding : public StandardHasher<TypesAndSettings> {
assert(num_slots >= kCoeffBits); assert(num_slots >= kCoeffBits);
if (num_slots > num_slots_allocated_) { if (num_slots > num_slots_allocated_) {
coeff_rows_.reset(new CoeffRow[num_slots]()); coeff_rows_.reset(new CoeffRow[num_slots]());
// Note: don't strictly have to zero-init result_rows, if (!TypesAndSettings::kHomogeneous) {
// except possible information leakage ;) // Note: don't strictly have to zero-init result_rows,
result_rows_.reset(new ResultRow[num_slots]()); // except possible information leakage, etc ;)
result_rows_.reset(new ResultRow[num_slots]());
}
num_slots_allocated_ = num_slots; num_slots_allocated_ = num_slots;
} else { } else {
for (Index i = 0; i < num_slots; ++i) { for (Index i = 0; i < num_slots; ++i) {
coeff_rows_[i] = 0; coeff_rows_[i] = 0;
// Note: don't strictly have to zero-init result_rows if (!TypesAndSettings::kHomogeneous) {
result_rows_[i] = 0; // Note: don't strictly have to zero-init result_rows,
// except possible information leakage, etc ;)
result_rows_[i] = 0;
}
} }
} }
num_starts_ = num_slots - kCoeffBits + 1; num_starts_ = num_slots - kCoeffBits + 1;
@ -480,10 +523,32 @@ class StandardBanding : public StandardHasher<TypesAndSettings> {
} }
inline void Prefetch(Index i) const { inline void Prefetch(Index i) const {
PREFETCH(&coeff_rows_[i], 1 /* rw */, 1 /* locality */); PREFETCH(&coeff_rows_[i], 1 /* rw */, 1 /* locality */);
PREFETCH(&result_rows_[i], 1 /* rw */, 1 /* locality */); if (!TypesAndSettings::kHomogeneous) {
PREFETCH(&result_rows_[i], 1 /* rw */, 1 /* locality */);
}
}
inline void LoadRow(Index i, CoeffRow* cr, ResultRow* rr,
bool for_back_subst) const {
*cr = coeff_rows_[i];
if (TypesAndSettings::kHomogeneous) {
if (for_back_subst && *cr == 0) {
// Cheap pseudorandom data to fill unconstrained solution rows
*rr = static_cast<ResultRow>(i * 0x9E3779B185EBCA87ULL);
} else {
*rr = 0;
}
} else {
*rr = result_rows_[i];
}
}
inline void StoreRow(Index i, CoeffRow cr, ResultRow rr) {
coeff_rows_[i] = cr;
if (TypesAndSettings::kHomogeneous) {
assert(rr == 0);
} else {
result_rows_[i] = rr;
}
} }
inline CoeffRow* CoeffRowPtr(Index i) { return &coeff_rows_[i]; }
inline ResultRow* ResultRowPtr(Index i) { return &result_rows_[i]; }
inline Index GetNumStarts() const { return num_starts_; } inline Index GetNumStarts() const { return num_starts_; }
// from concept BacktrackStorage, for when backtracking is used // from concept BacktrackStorage, for when backtracking is used
@ -554,6 +619,10 @@ class StandardBanding : public StandardHasher<TypesAndSettings> {
return count; return count;
} }
// Returns whether a row is "occupied" in the banding (non-zero
// coefficients stored). (Only recommended for debug/test)
bool IsOccupied(Index i) { return coeff_rows_[i] != 0; }
// ******************************************************************** // ********************************************************************
// High-level API // High-level API
@ -608,54 +677,6 @@ class StandardBanding : public StandardHasher<TypesAndSettings> {
return false; return false;
} }
// ********************************************************************
// Static high-level API
// Based on data from FindOccupancyForSuccessRate in ribbon_test,
// returns a number of slots for a given number of entries to add
// that should have roughly 95% or better chance of successful
// construction per seed. Does NOT do rounding for InterleavedSoln;
// call RoundUpNumSlots for that.
//
// num_to_add should not exceed roughly 2/3rds of the maximum value
// of the Index type to avoid overflow.
static Index GetNumSlotsFor95PctSuccess(Index num_to_add) {
if (num_to_add == 0) {
return 0;
}
double factor = GetFactorFor95PctSuccess(num_to_add);
Index num_slots = static_cast<Index>(num_to_add * factor);
assert(num_slots >= num_to_add);
return num_slots;
}
// Based on data from FindOccupancyForSuccessRate in ribbon_test,
// given a number of entries to add, returns a space overhead factor
// (slots divided by num_to_add) that should have roughly 95% or better
// chance of successful construction per seed. Does NOT do rounding for
// InterleavedSoln; call RoundUpNumSlots for that.
//
// The reason that num_to_add is needed is that Ribbon filters of a
// particular CoeffRow size do not scale infinitely.
static double GetFactorFor95PctSuccess(Index num_to_add) {
double log2_num_to_add = std::log(num_to_add) * 1.442695;
if (kCoeffBits == 64) {
if (TypesAndSettings::kUseSmash) {
return 1.02 + std::max(log2_num_to_add - 8.5, 0.0) * 0.009;
} else {
return 1.05 + std::max(log2_num_to_add - 11.0, 0.0) * 0.009;
}
} else {
// Currently only support 64 and 128
assert(kCoeffBits == 128);
if (TypesAndSettings::kUseSmash) {
return 1.01 + std::max(log2_num_to_add - 10.0, 0.0) * 0.0042;
} else {
return 1.02 + std::max(log2_num_to_add - 12.0, 0.0) * 0.0042;
}
}
}
protected: protected:
// TODO: explore combining in a struct // TODO: explore combining in a struct
std::unique_ptr<CoeffRow[]> coeff_rows_; std::unique_ptr<CoeffRow[]> coeff_rows_;
@ -716,8 +737,8 @@ class InMemSimpleSolution {
} }
template <typename PhsfQueryHasher> template <typename PhsfQueryHasher>
ResultRow PhsfQuery(const Key& input, const PhsfQueryHasher& hasher) { ResultRow PhsfQuery(const Key& input, const PhsfQueryHasher& hasher) const {
assert(!TypesAndSettings::kIsFilter); // assert(!TypesAndSettings::kIsFilter); Can be useful in testing
if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) { if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) {
// Unusual // Unusual
return 0; return 0;
@ -728,7 +749,7 @@ class InMemSimpleSolution {
} }
template <typename FilterQueryHasher> template <typename FilterQueryHasher>
bool FilterQuery(const Key& input, const FilterQueryHasher& hasher) { bool FilterQuery(const Key& input, const FilterQueryHasher& hasher) const {
assert(TypesAndSettings::kIsFilter); assert(TypesAndSettings::kIsFilter);
if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) { if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) {
// Unusual. Zero starts presumes no keys added -> always false // Unusual. Zero starts presumes no keys added -> always false
@ -740,7 +761,7 @@ class InMemSimpleSolution {
} }
} }
double ExpectedFpRate() { double ExpectedFpRate() const {
assert(TypesAndSettings::kIsFilter); assert(TypesAndSettings::kIsFilter);
if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) { if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) {
// Unusual, but we don't have FPs if we always return false. // Unusual, but we don't have FPs if we always return false.
@ -752,6 +773,20 @@ class InMemSimpleSolution {
return std::pow(0.5, 8U * sizeof(ResultRow)); return std::pow(0.5, 8U * sizeof(ResultRow));
} }
// ********************************************************************
// Static high-level API
// Round up to a number of slots supported by this structure. Note that
// this needs to be must be taken into account for the banding if this
// solution layout/storage is to be used.
static Index RoundUpNumSlots(Index num_slots) {
// Must be at least kCoeffBits for at least one start
// Or if not smash, even more because hashing not equipped
// for stacking up so many entries on a single start location
auto min_slots = kCoeffBits * (TypesAndSettings::kUseSmash ? 1 : 2);
return std::max(num_slots, static_cast<Index>(min_slots));
}
protected: protected:
// We generally store "starts" instead of slots for speed of GetStart(), // We generally store "starts" instead of slots for speed of GetStart(),
// as in StandardHasher. // as in StandardHasher.
@ -853,8 +888,8 @@ class SerializableInterleavedSolution {
} }
template <typename PhsfQueryHasher> template <typename PhsfQueryHasher>
ResultRow PhsfQuery(const Key& input, const PhsfQueryHasher& hasher) { ResultRow PhsfQuery(const Key& input, const PhsfQueryHasher& hasher) const {
assert(!TypesAndSettings::kIsFilter); // assert(!TypesAndSettings::kIsFilter); Can be useful in testing
if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) { if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) {
// Unusual // Unusual
return 0; return 0;
@ -873,7 +908,7 @@ class SerializableInterleavedSolution {
} }
template <typename FilterQueryHasher> template <typename FilterQueryHasher>
bool FilterQuery(const Key& input, const FilterQueryHasher& hasher) { bool FilterQuery(const Key& input, const FilterQueryHasher& hasher) const {
assert(TypesAndSettings::kIsFilter); assert(TypesAndSettings::kIsFilter);
if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) { if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) {
// Unusual. Zero starts presumes no keys added -> always false // Unusual. Zero starts presumes no keys added -> always false
@ -893,7 +928,7 @@ class SerializableInterleavedSolution {
} }
} }
double ExpectedFpRate() { double ExpectedFpRate() const {
assert(TypesAndSettings::kIsFilter); assert(TypesAndSettings::kIsFilter);
if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) { if (TypesAndSettings::kAllowZeroStarts && num_starts_ == 0) {
// Unusual. Zero starts presumes no keys added -> always false // Unusual. Zero starts presumes no keys added -> always false

File diff suppressed because it is too large Load Diff