Commit c9c55642 authored by Swaroop Manjunath's avatar Swaroop Manjunath Committed by Facebook GitHub Bot

Eliminate need for reading upper bound at construction.

Summary:
# Note: This is a resubmission after reverting this change.
The failing `DCHECK` has been fixed with an explicit cast to `size_t` to accommodate long lists.

# Summary

This diff introduces the following changes to the EliasFanoReader implementation.

- `EliasFanoReader` no longer requires knowledge of the last value in the list at construction time. This removes the need to access the last byte of the encoded list.
- Operations in `UpperBitsReader` are now responsible for ensuring validity.
- Removes constraint that the last set bit in upperBits must be in the last byte of the encoded list.

In addition, the diff also extends the unit-tests for Elias Fano coding to add arbitrary extensions to the upperBound at construction to ensure that additional 0-blocks at the end of the list do not affect the behavior of the reader.

Reviewed By: ot, luciang

Differential Revision: D22211304

fbshipit-source-id: 0dbe904c9fd8cd9568a480355e5e6a4525922966
parent 01da43c2
...@@ -24,9 +24,11 @@ ...@@ -24,9 +24,11 @@
#pragma once #pragma once
#include <algorithm> #include <algorithm>
#include <cstddef>
#include <cstdlib> #include <cstdlib>
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
#include <utility>
#include <folly/Likely.h> #include <folly/Likely.h>
#include <folly/Portability.h> #include <folly/Portability.h>
...@@ -354,18 +356,24 @@ struct EliasFanoEncoderV2< ...@@ -354,18 +356,24 @@ struct EliasFanoEncoderV2<
namespace detail { namespace detail {
template <class Encoder, class Instructions, class SizeType> template <
class Encoder,
class Instructions,
class SizeType,
bool kUnchecked = false>
class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
SkipPointers<Encoder::skipQuantum> { SkipPointers<Encoder::skipQuantum> {
typedef typename Encoder::SkipValueType SkipValueType; using SkipValueType = typename Encoder::SkipValueType;
public: public:
typedef typename Encoder::ValueType ValueType; using ValueType = typename Encoder::ValueType;
explicit UpperBitsReader(const typename Encoder::CompressedList& list) explicit UpperBitsReader(const typename Encoder::CompressedList& list)
: ForwardPointers<Encoder::forwardQuantum>(list.forwardPointers), : ForwardPointers<Encoder::forwardQuantum>(list.forwardPointers),
SkipPointers<Encoder::skipQuantum>(list.skipPointers), SkipPointers<Encoder::skipQuantum>(list.skipPointers),
start_(list.upper) { start_(list.upper),
size_(list.size),
upperBound_(8 * list.upperSizeBytes - size_) {
reset(); reset();
} }
...@@ -380,11 +388,23 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -380,11 +388,23 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
FOLLY_ALWAYS_INLINE SizeType position() const { FOLLY_ALWAYS_INLINE SizeType position() const {
return position_; return position_;
} }
FOLLY_ALWAYS_INLINE ValueType value() const { FOLLY_ALWAYS_INLINE ValueType value() const {
return value_; return value_;
} }
FOLLY_ALWAYS_INLINE ValueType previous() { FOLLY_ALWAYS_INLINE bool valid() const {
return position() < size();
}
FOLLY_ALWAYS_INLINE SizeType size() const {
return size_;
}
FOLLY_ALWAYS_INLINE bool previous() {
if (!kUnchecked && UNLIKELY(position() == 0)) {
return false;
}
size_t inner; size_t inner;
block_t block; block_t block;
getPreviousInfo(block, inner, outer_); getPreviousInfo(block, inner, outer_);
...@@ -394,7 +414,12 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -394,7 +414,12 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
return setValue(inner); return setValue(inner);
} }
FOLLY_ALWAYS_INLINE ValueType next() { // Returns `true` if the next position is within bounds.
FOLLY_ALWAYS_INLINE bool next() {
if (!kUnchecked && UNLIKELY(position() + 1 >= size())) {
return setDone();
}
// Skip to the first non-zero block. // Skip to the first non-zero block.
while (block_ == 0) { while (block_ == 0) {
outer_ += sizeof(block_t); outer_ += sizeof(block_t);
...@@ -404,12 +429,14 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -404,12 +429,14 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
++position_; ++position_;
size_t inner = Instructions::ctz(block_); size_t inner = Instructions::ctz(block_);
block_ = Instructions::blsr(block_); block_ = Instructions::blsr(block_);
return setValue(inner); return setValue(inner);
} }
FOLLY_ALWAYS_INLINE ValueType skip(SizeType n) { FOLLY_ALWAYS_INLINE bool skip(SizeType n) {
DCHECK_GT(n, 0); DCHECK_GT(n, 0);
if (!kUnchecked && UNLIKELY(position_ + n >= size())) {
return setDone();
}
position_ += n; // n 1-bits will be read. position_ += n; // n 1-bits will be read.
...@@ -435,15 +462,18 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -435,15 +462,18 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
DCHECK_GT(n, 0); DCHECK_GT(n, 0);
size_t inner = select64<Instructions>(block_, n - 1); size_t inner = select64<Instructions>(block_, n - 1);
block_ &= (block_t(-1) << inner) << 1; block_ &= (block_t(-1) << inner) << 1;
return setValue(inner); return setValue(inner);
} }
// Skip to the first element that is >= v and located *after* the current // Skip to the first element that is >= v and located *after* the current
// one (so even if current value equals v, position will be increased by 1). // one (so even if current value equals v, position will be increased by 1).
FOLLY_ALWAYS_INLINE ValueType skipToNext(ValueType v) { FOLLY_ALWAYS_INLINE bool skipToNext(ValueType v) {
DCHECK_GE(v, value_); DCHECK_GE(v, value_);
if (!kUnchecked && UNLIKELY(v > upperBound_)) {
return setDone();
}
// Use skip pointer. // Use skip pointer.
if (Encoder::skipQuantum > 0 && if (Encoder::skipQuantum > 0 &&
UNLIKELY(v >= value_ + Encoder::skipQuantum)) { UNLIKELY(v >= value_ + Encoder::skipQuantum)) {
...@@ -451,6 +481,12 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -451,6 +481,12 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
const size_t dest = folly::loadUnaligned<SkipValueType>( const size_t dest = folly::loadUnaligned<SkipValueType>(
this->skipPointers_ + (steps - 1) * sizeof(SkipValueType)); this->skipPointers_ + (steps - 1) * sizeof(SkipValueType));
DCHECK_LE(dest, size());
// Check if we've reached the end.
if (!kUnchecked && UNLIKELY(dest == size())) {
return setDone();
}
reposition(dest + Encoder::skipQuantum * steps); reposition(dest + Encoder::skipQuantum * steps);
position_ = dest - 1; position_ = dest - 1;
...@@ -470,6 +506,7 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -470,6 +506,7 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
skip -= cnt; skip -= cnt;
position_ += kBitsPerBlock - cnt; position_ += kBitsPerBlock - cnt;
outer_ += sizeof(block_t); outer_ += sizeof(block_t);
DCHECK_LT(outer_, (static_cast<size_t>(upperBound_) + size()) / 8);
block_ = folly::loadUnaligned<block_t>(start_ + outer_); block_ = folly::loadUnaligned<block_t>(start_ + outer_);
} }
...@@ -479,17 +516,24 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -479,17 +516,24 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
block_ &= block_t(-1) << inner; block_ &= block_t(-1) << inner;
} }
next(); DCHECK_LT(position() + 1, size() + 1);
return value_; return next();
} }
/** /**
* Prepare to skip to `value`. This is a constant-time operation that will * Try to prepare to skip to value. This is a constant-time operation
* prefetch memory required for a `skipTo(value)` call. * that will attempt to prefetch memory required for a subsequent
* skipTo(value) call if the value to skip to is within this list.
* *
* @return position of reader * Returns:
* {true, position of the reader} if the skip is valid,
* {false, size()} otherwise.
*/ */
FOLLY_ALWAYS_INLINE SizeType prepareSkipTo(ValueType v) const { FOLLY_ALWAYS_INLINE std::pair<bool, SizeType> prepareSkipTo(
ValueType v) const {
if (!kUnchecked && UNLIKELY(v > upperBound_)) {
return std::make_pair(false, size());
}
auto position = position_; auto position = position_;
if (Encoder::skipQuantum > 0 && v >= value_ + Encoder::skipQuantum) { if (Encoder::skipQuantum > 0 && v >= value_ + Encoder::skipQuantum) {
...@@ -498,6 +542,12 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -498,6 +542,12 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
const size_t dest = folly::loadUnaligned<SkipValueType>( const size_t dest = folly::loadUnaligned<SkipValueType>(
this->skipPointers_ + (steps - 1) * sizeof(SkipValueType)); this->skipPointers_ + (steps - 1) * sizeof(SkipValueType));
DCHECK_LE(dest, size());
// Check if we're going out of bounds.
if (!kUnchecked && UNLIKELY(dest == size())) {
return std::make_pair(false, size());
}
position = dest - 1; position = dest - 1;
outer = (dest + Encoder::skipQuantum * steps) / 8; outer = (dest + Encoder::skipQuantum * steps) / 8;
...@@ -512,7 +562,7 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -512,7 +562,7 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
__builtin_prefetch(addr + kCacheLineSize); __builtin_prefetch(addr + kCacheLineSize);
} }
return position; return std::make_pair(true, position);
} }
FOLLY_ALWAYS_INLINE ValueType previousValue() const { FOLLY_ALWAYS_INLINE ValueType previousValue() const {
...@@ -534,14 +584,15 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -534,14 +584,15 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
return (start_[bitPos / 8] & (1 << (bitPos % 8))) == 0; return (start_[bitPos / 8] & (1 << (bitPos % 8))) == 0;
} }
FOLLY_ALWAYS_INLINE void setDone(SizeType endPos) { FOLLY_ALWAYS_INLINE bool setDone() {
position_ = endPos; position_ = size_;
return false;
} }
private: private:
FOLLY_ALWAYS_INLINE ValueType setValue(size_t inner) { FOLLY_ALWAYS_INLINE bool setValue(size_t inner) {
value_ = static_cast<ValueType>(8 * outer_ + inner - position_); value_ = static_cast<ValueType>(8 * outer_ + inner - position_);
return value_; return true;
} }
FOLLY_ALWAYS_INLINE void reposition(SizeType dest) { FOLLY_ALWAYS_INLINE void reposition(SizeType dest) {
...@@ -573,6 +624,8 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>, ...@@ -573,6 +624,8 @@ class UpperBitsReader : ForwardPointers<Encoder::forwardQuantum>,
} }
const unsigned char* const start_; const unsigned char* const start_;
const SizeType size_; // Size of the list.
const ValueType upperBound_; // Upper bound of values in this list.
block_t block_; block_t block_;
SizeType position_; // Index of current value (= #reads - 1). SizeType position_; // Index of current value (= #reads - 1).
OuterType outer_; // Outer offset: number of consumed bytes in upper. OuterType outer_; // Outer offset: number of consumed bytes in upper.
...@@ -598,24 +651,8 @@ class EliasFanoReader { ...@@ -598,24 +651,8 @@ class EliasFanoReader {
typedef typename Encoder::ValueType ValueType; typedef typename Encoder::ValueType ValueType;
explicit EliasFanoReader(const typename Encoder::CompressedList& list) explicit EliasFanoReader(const typename Encoder::CompressedList& list)
: upper_(list), : upper_(list), lower_(list.lower), numLowerBits_(list.numLowerBits) {
lower_(list.lower),
size_(list.size),
numLowerBits_(list.numLowerBits) {
DCHECK(Instructions::supported()); DCHECK(Instructions::supported());
// To avoid extra branching during skipTo() while reading
// upper sequence we need to know the last element.
// If kUnchecked == true, we do not check that skipTo() is called
// within the bounds, so we can avoid initializing lastValue_.
if (kUnchecked || UNLIKELY(list.size == 0)) {
lastValue_ = 0;
return;
}
ValueType lastUpperValue = ValueType(8 * list.upperSizeBytes - size_);
auto it = list.upper + list.upperSizeBytes - 1;
DCHECK_NE(*it, 0);
lastUpperValue -= 8 - folly::findLastSet(*it);
lastValue_ = readLowerPart(size_ - 1) | (lastUpperValue << numLowerBits_);
} }
void reset() { void reset() {
...@@ -624,25 +661,21 @@ class EliasFanoReader { ...@@ -624,25 +661,21 @@ class EliasFanoReader {
} }
bool previous() { bool previous() {
if (!kUnchecked && UNLIKELY(position() == 0)) { if (LIKELY(upper_.previous())) {
setCurrentValue();
return true;
}
reset(); reset();
return false; return false;
} }
upper_.previous();
value_ =
readLowerPart(upper_.position()) | (upper_.value() << numLowerBits_);
return true;
}
bool next() { bool next() {
if (!kUnchecked && UNLIKELY(position() + 1 >= size_)) { if (LIKELY(upper_.next())) {
return setDone(); setCurrentValue();
}
upper_.next();
value_ =
readLowerPart(upper_.position()) | (upper_.value() << numLowerBits_);
return true; return true;
} }
return setDone();
}
/** /**
* Advances by n elements. n = 0 is allowed and has no effect. Returns false * Advances by n elements. n = 0 is allowed and has no effect. Returns false
...@@ -653,15 +686,12 @@ class EliasFanoReader { ...@@ -653,15 +686,12 @@ class EliasFanoReader {
return valid(); return valid();
} }
if (kUnchecked || LIKELY(position() + n < size_)) { if (!upper_.skip(n)) {
upper_.skip(n);
value_ =
readLowerPart(upper_.position()) | (upper_.value() << numLowerBits_);
return true;
}
return setDone(); return setDone();
} }
setCurrentValue();
return true;
}
/** /**
* Skips to the first element >= value whose position is greater or equal to * Skips to the first element >= value whose position is greater or equal to
...@@ -671,19 +701,26 @@ class EliasFanoReader { ...@@ -671,19 +701,26 @@ class EliasFanoReader {
bool skipTo(ValueType value) { bool skipTo(ValueType value) {
if (value != kInvalidValue) { if (value != kInvalidValue) {
DCHECK_GE(value + 1, value_ + 1); DCHECK_GE(value + 1, value_ + 1);
if (UNLIKELY(value == value_)) {
return true;
} }
}
ValueType upperValue = value >> numLowerBits_;
if (!kUnchecked && UNLIKELY(value > lastValue_)) { if (UNLIKELY(!upper_.skipToNext(upperValue))) {
// The value is beyond the last value in the list.
return setDone(); return setDone();
} else if (UNLIKELY(value == value_)) {
return true;
} }
ValueType upperValue = value >> numLowerBits_; do {
upper_.skipToNext(upperValue); setCurrentValue();
iterateTo(value); if (LIKELY(value_ >= value)) {
return true; return true;
} }
} while (LIKELY(upper_.next()));
return setDone();
}
/** /**
* Prepare to skip to `value` by prefetching appropriate memory in both the * Prepare to skip to `value` by prefetching appropriate memory in both the
...@@ -693,31 +730,33 @@ class EliasFanoReader { ...@@ -693,31 +730,33 @@ class EliasFanoReader {
// Also works when value_ == kInvalidValue. // Also works when value_ == kInvalidValue.
if (value != kInvalidValue) { if (value != kInvalidValue) {
DCHECK_GE(value + 1, value_ + 1); DCHECK_GE(value + 1, value_ + 1);
} if (UNLIKELY(value == value_)) {
if ((!kUnchecked && value > lastValue_) || (value == value_)) {
return; return;
} }
}
// Do minimal computation required to prefetch address used in // Do minimal computation required to prefetch address used in
// `readLowerPart()`. // `readLowerPart()`.
ValueType upperValue = (value >> numLowerBits_); ValueType upperValue = (value >> numLowerBits_);
const auto upperPosition = upper_.prepareSkipTo(upperValue); const auto skipDetails = upper_.prepareSkipTo(upperValue);
const auto addr = lower_ + (upperPosition * numLowerBits_ / 8); // If we have a valid skip position, prefetch the lower bits for it.
if (LIKELY(skipDetails.first)) {
const auto addr = lower_ + (skipDetails.second * numLowerBits_ / 8);
__builtin_prefetch(addr); __builtin_prefetch(addr);
__builtin_prefetch(addr + kCacheLineSize); __builtin_prefetch(addr + kCacheLineSize);
} }
}
/** /**
* Jumps to the element at position n. The reader can be in any state. Returns * Jumps to the element at position n. The reader can be in any state. Returns
* false if n >= size(). * false if n >= size().
*/ */
bool jump(SizeType n) { bool jump(SizeType n) {
if (n + 1 < upper_.position() + 1) { // Also works if position() == -1. if (n + 1 < position() + 1) { // Also works if position() == -1.
reset(); reset();
n += 1; // Initial position is -1. n += 1; // Initial position is -1.
} else { } else {
n -= upper_.position(); n -= position();
} }
return skip(n); return skip(n);
} }
...@@ -738,7 +777,7 @@ class EliasFanoReader { ...@@ -738,7 +777,7 @@ class EliasFanoReader {
// We might be in the middle of a run, iterate backwards to the beginning. // We might be in the middle of a run, iterate backwards to the beginning.
auto valueLower = Instructions::bzhi(value_, numLowerBits_); auto valueLower = Instructions::bzhi(value_, numLowerBits_);
while (!upper_.isAtBeginningOfRun() && while (!upper_.isAtBeginningOfRun() &&
readLowerPart(upper_.position() - 1) == valueLower) { readLowerPart(position() - 1) == valueLower) {
upper_.previous(); upper_.previous();
} }
return true; return true;
...@@ -753,20 +792,15 @@ class EliasFanoReader { ...@@ -753,20 +792,15 @@ class EliasFanoReader {
return skipTo(value); return skipTo(value);
} }
ValueType lastValue() const {
CHECK(!kUnchecked);
return lastValue_;
}
ValueType previousValue() const { ValueType previousValue() const {
DCHECK_GT(position(), 0); DCHECK_GT(position(), 0);
DCHECK_LT(position(), size()); DCHECK_LT(position(), size());
return readLowerPart(upper_.position() - 1) | return readLowerPart(position() - 1) |
(upper_.previousValue() << numLowerBits_); (upper_.previousValue() << numLowerBits_);
} }
SizeType size() const { SizeType size() const {
return size_; return upper_.size();
} }
bool valid() const { bool valid() const {
...@@ -787,37 +821,27 @@ class EliasFanoReader { ...@@ -787,37 +821,27 @@ class EliasFanoReader {
FOLLY_ALWAYS_INLINE bool setDone() { FOLLY_ALWAYS_INLINE bool setDone() {
value_ = kInvalidValue; value_ = kInvalidValue;
upper_.setDone(size_); return upper_.setDone();
return false; }
FOLLY_ALWAYS_INLINE void setCurrentValue() {
value_ = readLowerPart(position()) | (upper_.value() << numLowerBits_);
} }
FOLLY_ALWAYS_INLINE ValueType readLowerPart(SizeType i) const { FOLLY_ALWAYS_INLINE ValueType readLowerPart(SizeType i) const {
DCHECK_LT(i, size_); DCHECK_LT(i, size());
const size_t pos = i * numLowerBits_; const size_t pos = i * numLowerBits_;
const unsigned char* ptr = lower_ + (pos / 8); const unsigned char* ptr = lower_ + (pos / 8);
const uint64_t ptrv = folly::loadUnaligned<uint64_t>(ptr); const uint64_t ptrv = folly::loadUnaligned<uint64_t>(ptr);
// This removes the branch in the fallback implementation of // This removes the branch in the fallback implementation of
// bzhi. The condition is verified at encoding time. // bzhi. The condition is verified at encoding time.
assume(numLowerBits_ < sizeof(ValueType) * 8); folly::assume(numLowerBits_ < sizeof(ValueType) * 8);
return Instructions::bzhi(ptrv >> (pos % 8), numLowerBits_); return Instructions::bzhi(ptrv >> (pos % 8), numLowerBits_);
} }
FOLLY_ALWAYS_INLINE void iterateTo(ValueType value) { detail::UpperBitsReader<Encoder, Instructions, SizeType, kUnchecked> upper_;
while (true) {
value_ =
readLowerPart(upper_.position()) | (upper_.value() << numLowerBits_);
if (LIKELY(value_ >= value)) {
break;
}
upper_.next();
}
}
detail::UpperBitsReader<Encoder, Instructions, SizeType> upper_;
const uint8_t* lower_; const uint8_t* lower_;
SizeType size_;
ValueType value_ = kInvalidValue; ValueType value_ = kInvalidValue;
ValueType lastValue_;
uint8_t numLowerBits_; uint8_t numLowerBits_;
}; };
......
...@@ -35,6 +35,11 @@ ...@@ -35,6 +35,11 @@
namespace folly { namespace folly {
namespace compression { namespace compression {
template <typename ValueType, class List>
folly::Optional<std::size_t> getUniverseUpperBound(const List& /* list */) {
return folly::none;
}
template <class URNG> template <class URNG>
std::vector<uint64_t> generateRandomList( std::vector<uint64_t> generateRandomList(
size_t n, size_t n,
...@@ -268,6 +273,16 @@ void testSkipTo(const std::vector<uint64_t>& data, const List& list) { ...@@ -268,6 +273,16 @@ void testSkipTo(const std::vector<uint64_t>& data, const List& list) {
EXPECT_EQ(reader.position(), reader.size()); EXPECT_EQ(reader.position(), reader.size());
EXPECT_FALSE(reader.next()); EXPECT_FALSE(reader.next());
} }
// Skip past the last element and before the upperBound.
using ValueType = typename Reader::ValueType;
if (const auto upperBound = getUniverseUpperBound<ValueType>(list);
upperBound && *upperBound != data.back()) {
Reader reader(list);
EXPECT_FALSE(reader.skipTo(*upperBound));
EXPECT_FALSE(reader.valid());
EXPECT_EQ(reader.position(), reader.size());
EXPECT_FALSE(reader.next());
}
} }
template <class Reader, class List> template <class Reader, class List>
...@@ -362,9 +377,19 @@ void testEmpty() { ...@@ -362,9 +377,19 @@ void testEmpty() {
} }
} }
// `upperBoundExtension` is required to inject additional 0-blocks
// at the end of the list. This allows us to test lists with a large gap between
// last element and universe upper bound, to exercise bounds-checking when
// skipping past the last element
template <class Reader, class Encoder> template <class Reader, class Encoder>
void testAll(const std::vector<uint64_t>& data) { void testAll(
auto list = Encoder::encode(data.begin(), data.end()); const std::vector<uint64_t>& data,
uint64_t upperBoundExtension = 0) {
Encoder encoder(data.size(), data.back() + upperBoundExtension);
for (const auto value : data) {
encoder.add(value);
}
auto list = encoder.finish();
testNext<Reader>(data, list); testNext<Reader>(data, list);
testSkip<Reader>(data, list); testSkip<Reader>(data, list);
testSkipTo<Reader>(data, list); testSkipTo<Reader>(data, list);
......
...@@ -21,11 +21,32 @@ ...@@ -21,11 +21,32 @@
#include <vector> #include <vector>
#include <folly/Benchmark.h> #include <folly/Benchmark.h>
#include <folly/Optional.h>
#include <folly/Random.h>
#include <folly/experimental/EliasFanoCoding.h> #include <folly/experimental/EliasFanoCoding.h>
#include <folly/experimental/Select64.h> #include <folly/experimental/Select64.h>
#include <folly/experimental/test/CodingTestUtils.h> #include <folly/experimental/test/CodingTestUtils.h>
#include <folly/init/Init.h> #include <folly/init/Init.h>
namespace folly {
namespace compression {
// Overload to help CodingTestUtils retrieve the universe upperbound
// of the list for certain test cases.
template <typename ValueType, typename T>
folly::Optional<std::size_t> getUniverseUpperBound(
const EliasFanoCompressedListBase<T>& list) {
constexpr ValueType maxUpperValue = std::numeric_limits<ValueType>::max();
const ValueType maxUpperBits = maxUpperValue >> list.numLowerBits;
const ValueType upperBitsUniverse = std::min(
static_cast<ValueType>(8 * list.upperSizeBytes - list.size),
maxUpperBits);
return (upperBitsUniverse << list.numLowerBits) |
((1 << list.numLowerBits) - 1);
}
} // namespace compression
} // namespace folly
using namespace folly::compression; using namespace folly::compression;
namespace { namespace {
...@@ -114,6 +135,13 @@ class EliasFanoCodingTest : public ::testing::Test { ...@@ -114,6 +135,13 @@ class EliasFanoCodingTest : public ::testing::Test {
// max() cannot be read, as it is assumed an invalid value. // max() cannot be read, as it is assumed an invalid value.
// TODO(ott): It should be possible to lift this constraint. // TODO(ott): It should be possible to lift this constraint.
testAll<Reader, Encoder>({0, 1, std::numeric_limits<uint32_t>::max() - 1}); testAll<Reader, Encoder>({0, 1, std::numeric_limits<uint32_t>::max() - 1});
// Test data with additional trailing 0s in the upperBits by extending
// the upper bound.
constexpr uint64_t minUpperBoundExtension = 2;
constexpr uint64_t maxUpperBoundExtension = 1024;
testAll<Reader, Encoder>(
generateRandomList(100 * 1000, 10 * 1000 * 1000),
folly::Random::rand32(minUpperBoundExtension, maxUpperBoundExtension));
} }
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment