Implement previousValue on EliasFanoReader
[folly.git] / folly / experimental / EliasFanoCoding.h
1 /*
2  * Copyright 2015 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * @author Philip Pronin (philipp@fb.com)
19  *
20  * Based on the paper by Sebastiano Vigna,
21  * "Quasi-succinct indices" (arxiv:1206.4300).
22  */
23
24 #ifndef FOLLY_EXPERIMENTAL_ELIAS_FANO_CODING_H
25 #define FOLLY_EXPERIMENTAL_ELIAS_FANO_CODING_H
26
27 #include <cstdlib>
28 #include <limits>
29 #include <type_traits>
30 #include <glog/logging.h>
31
32 #include <folly/Bits.h>
33 #include <folly/CpuId.h>
34 #include <folly/Likely.h>
35 #include <folly/Portability.h>
36 #include <folly/Range.h>
37 #include <folly/experimental/Select64.h>
38
39 #ifndef __GNUC__
40 #error EliasFanoCoding.h requires GCC
41 #endif
42
43 #if !FOLLY_X64
44 #error EliasFanoCoding.h requires x86_64
45 #endif
46
47 #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
48 #error EliasFanoCoding.h requires little endianness
49 #endif
50
51 namespace folly { namespace compression {
52
53 template <class Pointer>
54 struct EliasFanoCompressedListBase {
55   EliasFanoCompressedListBase() = default;
56
57   template <class OtherPointer>
58   EliasFanoCompressedListBase(
59     const EliasFanoCompressedListBase<OtherPointer>& other)
60       : size(other.size),
61         numLowerBits(other.numLowerBits),
62         data(other.data),
63         skipPointers(reinterpret_cast<Pointer>(other.skipPointers)),
64         forwardPointers(reinterpret_cast<Pointer>(other.forwardPointers)),
65         lower(reinterpret_cast<Pointer>(other.lower)),
66         upper(reinterpret_cast<Pointer>(other.upper)) {
67   }
68
69   void free() {
70     ::free(const_cast<unsigned char*>(data.data()));
71   }
72
73   size_t upperSize() const {
74     return data.end() - upper;
75   }
76
77   size_t size = 0;
78   uint8_t numLowerBits = 0;
79
80   // WARNING: EliasFanoCompressedList has no ownership of data. The 7
81   // bytes following the last byte should be readable.
82   folly::Range<Pointer> data;
83
84   Pointer skipPointers = nullptr;
85   Pointer forwardPointers = nullptr;
86   Pointer lower = nullptr;
87   Pointer upper = nullptr;
88 };
89
90 typedef EliasFanoCompressedListBase<const uint8_t*> EliasFanoCompressedList;
91 typedef EliasFanoCompressedListBase<uint8_t*> MutableEliasFanoCompressedList;
92
93 template <class Value,
94           class SkipValue = size_t,
95           size_t kSkipQuantum = 0,     // 0 = disabled
96           size_t kForwardQuantum = 0>  // 0 = disabled
97 struct EliasFanoEncoderV2 {
98   static_assert(std::is_integral<Value>::value &&
99                 std::is_unsigned<Value>::value,
100                 "Value should be unsigned integral");
101
102   typedef EliasFanoCompressedList CompressedList;
103
104   typedef Value ValueType;
105   typedef SkipValue SkipValueType;
106   struct Layout;
107
108   static constexpr size_t skipQuantum = kSkipQuantum;
109   static constexpr size_t forwardQuantum = kForwardQuantum;
110
111   static uint8_t defaultNumLowerBits(size_t upperBound, size_t size) {
112     if (size == 0 || upperBound < size) {
113       return 0;
114     }
115     // floor(log(upperBound / size));
116     return folly::findLastSet(upperBound / size) - 1;
117   }
118
119   // Requires: input range (begin, end) is sorted (encoding
120   // crashes if it's not).
121   // WARNING: encode() mallocates EliasFanoCompressedList::data. As
122   // EliasFanoCompressedList has no ownership of it, you need to call
123   // free() explicitly.
124   template <class RandomAccessIterator>
125   static EliasFanoCompressedList encode(RandomAccessIterator begin,
126                                         RandomAccessIterator end) {
127     if (begin == end) {
128       return EliasFanoCompressedList();
129     }
130     EliasFanoEncoderV2 encoder(end - begin, *(end - 1));
131     for (; begin != end; ++begin) {
132       encoder.add(*begin);
133     }
134     return encoder.finish();
135   }
136
137   explicit EliasFanoEncoderV2(const MutableEliasFanoCompressedList& result)
138       : lower_(result.lower),
139         upper_(result.upper),
140         skipPointers_(reinterpret_cast<SkipValueType*>(
141                         result.skipPointers)),
142         forwardPointers_(reinterpret_cast<SkipValueType*>(
143                            result.forwardPointers)),
144         result_(result) {
145     memset(result.data.data(), 0, result.data.size());
146   }
147
148   EliasFanoEncoderV2(size_t size, ValueType upperBound)
149       : EliasFanoEncoderV2(
150             Layout::fromUpperBoundAndSize(upperBound, size).allocList()) {
151   }
152
153   void add(ValueType value) {
154     CHECK_GE(value, lastValue_);
155
156     const auto numLowerBits = result_.numLowerBits;
157     const ValueType upperBits = value >> numLowerBits;
158
159     // Upper sequence consists of upperBits 0-bits and (size_ + 1) 1-bits.
160     const size_t pos = upperBits + size_;
161     upper_[pos / 8] |= 1U << (pos % 8);
162     // Append numLowerBits bits to lower sequence.
163     if (numLowerBits != 0) {
164       const ValueType lowerBits = value & ((ValueType(1) << numLowerBits) - 1);
165       writeBits56(lower_, size_ * numLowerBits, numLowerBits, lowerBits);
166     }
167
168     /* static */ if (skipQuantum != 0) {
169       while ((skipPointersSize_ + 1) * skipQuantum <= upperBits) {
170         // Store the number of preceding 1-bits.
171         skipPointers_[skipPointersSize_++] = size_;
172       }
173     }
174
175     /* static */ if (forwardQuantum != 0) {
176       if ((size_ + 1) % forwardQuantum == 0) {
177         const auto pos = size_ / forwardQuantum;
178         // Store the number of preceding 0-bits.
179         forwardPointers_[pos] = upperBits;
180       }
181     }
182
183     lastValue_ = value;
184     ++size_;
185   }
186
187   const EliasFanoCompressedList& finish() const {
188     CHECK_EQ(size_, result_.size);
189     return result_;
190   }
191
192  private:
193   // Writes value (with len up to 56 bits) to data starting at pos-th bit.
194   static void writeBits56(unsigned char* data, size_t pos,
195                           uint8_t len, uint64_t value) {
196     DCHECK_LE(uint32_t(len), 56);
197     DCHECK_EQ(0, value & ~((uint64_t(1) << len) - 1));
198     unsigned char* const ptr = data + (pos / 8);
199     uint64_t ptrv = folly::loadUnaligned<uint64_t>(ptr);
200     ptrv |= value << (pos % 8);
201     folly::storeUnaligned<uint64_t>(ptr, ptrv);
202   }
203
204   unsigned char* lower_ = nullptr;
205   unsigned char* upper_ = nullptr;
206   SkipValueType* skipPointers_ = nullptr;
207   SkipValueType* forwardPointers_ = nullptr;
208
209   ValueType lastValue_ = 0;
210   size_t size_ = 0;
211   size_t skipPointersSize_ = 0;
212
213   EliasFanoCompressedList result_;
214 };
215
216 template <class Value,
217           class SkipValue,
218           size_t kSkipQuantum,
219           size_t kForwardQuantum>
220 struct EliasFanoEncoderV2<Value,
221                           SkipValue,
222                           kSkipQuantum,
223                           kForwardQuantum>::Layout {
224   static Layout fromUpperBoundAndSize(size_t upperBound, size_t size) {
225     // numLowerBits can be at most 56 because of detail::writeBits56.
226     const uint8_t numLowerBits = std::min(defaultNumLowerBits(upperBound,
227                                                               size),
228                                           uint8_t(56));
229     // *** Upper bits.
230     // Upper bits are stored using unary delta encoding.
231     // For example, (3 5 5 9) will be encoded as 1000011001000_2.
232     const size_t upperSizeBits =
233       (upperBound >> numLowerBits) +  // Number of 0-bits to be stored.
234       size;                           // 1-bits.
235     const size_t upper = (upperSizeBits + 7) / 8;
236
237     // *** Validity checks.
238     // Shift by numLowerBits must be valid.
239     CHECK_LT(numLowerBits, 8 * sizeof(Value));
240     CHECK_LT(size, std::numeric_limits<SkipValueType>::max());
241     CHECK_LT(upperBound >> numLowerBits,
242              std::numeric_limits<SkipValueType>::max());
243
244     return fromInternalSizes(numLowerBits, upper, size);
245   }
246
247   static Layout fromInternalSizes(uint8_t numLowerBits,
248                                   size_t upper,
249                                   size_t size) {
250     Layout layout;
251     layout.size = size;
252     layout.numLowerBits = numLowerBits;
253
254     layout.lower = (numLowerBits * size + 7) / 8;
255     layout.upper = upper;
256
257     // *** Skip pointers.
258     // Store (1-indexed) position of every skipQuantum-th
259     // 0-bit in upper bits sequence.
260     /* static */ if (skipQuantum != 0) {
261       // 8 * upper is used here instead of upperSizeBits, as that is
262       // more serialization-friendly way (upperSizeBits doesn't need
263       // to be known by this function, unlike upper).
264
265       // '?: 1' is a workaround for false 'division by zero'
266       // compile-time error.
267       size_t numSkipPointers = (8 * upper - size) / (skipQuantum ?: 1);
268       layout.skipPointers = numSkipPointers * sizeof(SkipValueType);
269     }
270
271     // *** Forward pointers.
272     // Store (1-indexed) position of every forwardQuantum-th
273     // 1-bit in upper bits sequence.
274     /* static */ if (forwardQuantum != 0) {
275       size_t numForwardPointers = size / (forwardQuantum ?: 1);
276       layout.forwardPointers = numForwardPointers * sizeof(SkipValueType);
277     }
278
279     return layout;
280   }
281
282   size_t bytes() const {
283     return lower + upper + skipPointers + forwardPointers;
284   }
285
286   template <typename Range>
287   EliasFanoCompressedListBase<typename Range::iterator>
288   openList(Range& buf) const {
289     EliasFanoCompressedListBase<typename Range::iterator> result;
290     result.size = size;
291     result.numLowerBits = numLowerBits;
292     result.data = buf.subpiece(0, bytes());
293
294     auto advance = [&] (size_t n) {
295       auto begin = buf.data();
296       buf.advance(n);
297       return begin;
298     };
299
300     result.skipPointers = advance(skipPointers);
301     result.forwardPointers = advance(forwardPointers);
302     result.lower = advance(lower);
303     result.upper = advance(upper);
304
305     return result;
306   }
307
308   MutableEliasFanoCompressedList allocList() const {
309     uint8_t* buf = nullptr;
310     // WARNING: Current read/write logic assumes that the 7 bytes
311     // following the last byte of lower and upper sequences are
312     // readable (stored value doesn't matter and won't be changed), so
313     // we allocate additional 7 bytes, but do not include them in size
314     // of returned value.
315     if (size > 0) {
316       buf = static_cast<uint8_t*>(malloc(bytes() + 7));
317     }
318     folly::MutableByteRange bufRange(buf, bytes());
319     return openList(bufRange);
320   }
321
322   size_t size = 0;
323   uint8_t numLowerBits = 0;
324
325   // Sizes in bytes.
326   size_t lower = 0;
327   size_t upper = 0;
328   size_t skipPointers = 0;
329   size_t forwardPointers = 0;
330 };
331
332 // NOTE: It's recommended to compile EF coding with -msse4.2, starting
333 // with Nehalem, Intel CPUs support POPCNT instruction and gcc will emit
334 // it for __builtin_popcountll intrinsic.
335 // But we provide an alternative way for the client code: it can switch to
336 // the appropriate version of EliasFanoReader<> in realtime (client should
337 // implement this switching logic itself) by specifying instruction set to
338 // use explicitly.
339 namespace instructions {
340
341 struct Default {
342   static bool supported(const folly::CpuId& cpuId = {}) {
343     return true;
344   }
345   static inline uint64_t popcount(uint64_t value) {
346     return __builtin_popcountll(value);
347   }
348   static inline int ctz(uint64_t value) {
349     DCHECK_GT(value, 0);
350     return __builtin_ctzll(value);
351   }
352   static inline int clz(uint64_t value) {
353     DCHECK_GT(value, 0);
354     return __builtin_clzll(value);
355   }
356   static inline uint64_t blsr(uint64_t value) {
357     return value & (value - 1);
358   }
359 };
360
361 struct Nehalem : public Default {
362   static bool supported(const folly::CpuId& cpuId = {}) {
363     return cpuId.popcnt();
364   }
365   static inline uint64_t popcount(uint64_t value) {
366     // POPCNT is supported starting with Intel Nehalem, AMD K10.
367     uint64_t result;
368     asm ("popcntq %1, %0" : "=r" (result) : "r" (value));
369     return result;
370   }
371 };
372
373 struct Haswell : public Nehalem {
374   static bool supported(const folly::CpuId& cpuId = {}) {
375     return Nehalem::supported(cpuId) && cpuId.bmi1();
376   }
377   static inline uint64_t blsr(uint64_t value) {
378     // BMI1 is supported starting with Intel Haswell, AMD Piledriver.
379     // BLSR combines two instuctions into one and reduces register pressure.
380     uint64_t result;
381     asm ("blsrq %1, %0" : "=r" (result) : "r" (value));
382     return result;
383   }
384 };
385
386 }  // namespace instructions
387
388 namespace detail {
389
390 template <class Encoder, class Instructions>
391 class UpperBitsReader {
392   typedef typename Encoder::SkipValueType SkipValueType;
393  public:
394   typedef typename Encoder::ValueType ValueType;
395
396   explicit UpperBitsReader(const EliasFanoCompressedList& list)
397     : forwardPointers_(list.forwardPointers),
398       skipPointers_(list.skipPointers),
399       start_(list.upper) {
400     reset();
401   }
402
403   void reset() {
404     block_ = start_ != nullptr ? folly::loadUnaligned<block_t>(start_) : 0;
405     outer_ = 0;
406     inner_ = -1;
407     position_ = -1;
408     value_ = 0;
409   }
410
411   size_t position() const { return position_; }
412   ValueType value() const { return value_; }
413
414   ValueType next() {
415     // Skip to the first non-zero block.
416     while (block_ == 0) {
417       outer_ += sizeof(block_t);
418       block_ = folly::loadUnaligned<block_t>(start_ + outer_);
419     }
420
421     ++position_;
422     inner_ = Instructions::ctz(block_);
423     block_ = Instructions::blsr(block_);
424
425     return setValue();
426   }
427
428   ValueType skip(size_t n) {
429     DCHECK_GT(n, 0);
430
431     position_ += n;  // n 1-bits will be read.
432
433     // Use forward pointer.
434     if (Encoder::forwardQuantum > 0 && n > Encoder::forwardQuantum) {
435       // Workaround to avoid 'division by zero' compile-time error.
436       constexpr size_t q = Encoder::forwardQuantum ?: 1;
437
438       const size_t steps = position_ / q;
439       const size_t dest =
440         folly::loadUnaligned<SkipValueType>(
441             forwardPointers_ + (steps - 1) * sizeof(SkipValueType));
442
443       reposition(dest + steps * q);
444       n = position_ + 1 - steps * q;  // n is > 0.
445       // Correct inner_ will be set at the end.
446     }
447
448     size_t cnt;
449     // Find necessary block.
450     while ((cnt = Instructions::popcount(block_)) < n) {
451       n -= cnt;
452       outer_ += sizeof(block_t);
453       block_ = folly::loadUnaligned<block_t>(start_ + outer_);
454     }
455
456     // Skip to the n-th one in the block.
457     DCHECK_GT(n, 0);
458     inner_ = select64<Instructions>(block_, n - 1);
459     block_ &= (block_t(-1) << inner_) << 1;
460
461     return setValue();
462   }
463
464   // Skip to the first element that is >= v and located *after* the current
465   // one (so even if current value equals v, position will be increased by 1).
466   ValueType skipToNext(ValueType v) {
467     DCHECK_GE(v, value_);
468
469     // Use skip pointer.
470     if (Encoder::skipQuantum > 0 && v >= value_ + Encoder::skipQuantum) {
471       // Workaround to avoid 'division by zero' compile-time error.
472       constexpr size_t q = Encoder::skipQuantum ?: 1;
473
474       const size_t steps = v / q;
475       const size_t dest =
476         folly::loadUnaligned<SkipValueType>(
477             skipPointers_ + (steps - 1) * sizeof(SkipValueType));
478
479       reposition(dest + q * steps);
480       position_ = dest - 1;
481
482       // Correct inner_ and value_ will be set during the next()
483       // call at the end.
484
485       // NOTE: Corresponding block of lower bits sequence may be
486       // prefetched here (via __builtin_prefetch), but experiments
487       // didn't show any significant improvements.
488     }
489
490     // Skip by blocks.
491     size_t cnt;
492     size_t skip = v - (8 * outer_ - position_ - 1);
493
494     constexpr size_t kBitsPerBlock = 8 * sizeof(block_t);
495     while ((cnt = Instructions::popcount(~block_)) < skip) {
496       skip -= cnt;
497       position_ += kBitsPerBlock - cnt;
498       outer_ += sizeof(block_t);
499       block_ = folly::loadUnaligned<block_t>(start_ + outer_);
500     }
501
502     if (LIKELY(skip)) {
503       auto inner = select64<Instructions>(~block_, skip - 1);
504       position_ += inner - skip + 1;
505       block_ &= block_t(-1) << inner;
506     }
507
508     next();
509     return value_;
510   }
511
512   ValueType jump(size_t n) {
513     if (Encoder::forwardQuantum == 0 || n <= Encoder::forwardQuantum) {
514       reset();
515     } else {
516       position_ = -1;  // Avoid reading the head, skip() will reposition.
517     }
518     return skip(n);
519   }
520
521   ValueType jumpToNext(ValueType v) {
522     if (Encoder::skipQuantum == 0 || v < Encoder::skipQuantum) {
523       reset();
524     } else {
525       value_ = 0;  // Avoid reading the head, skipToNext() will reposition.
526     }
527     return skipToNext(v);
528   }
529
530   ValueType previousValue() const {
531     DCHECK_NE(position(), -1);
532     DCHECK_GT(position(), 0);
533
534     size_t outer = outer_;
535     block_t block = folly::loadUnaligned<block_t>(start_ + outer);
536     block &= (block_t(1) << inner_) - 1;
537
538     while (UNLIKELY(block == 0)) {
539       DCHECK_GE(outer, sizeof(block_t));
540       outer -= sizeof(block_t);
541       block = folly::loadUnaligned<block_t>(start_ + outer);
542     }
543
544     auto inner = 8 * sizeof(block_t) - 1 - Instructions::clz(block);
545     return static_cast<ValueType>(8 * outer + inner - (position_ - 1));
546   }
547
548   void setDone(size_t endPos) {
549     position_ = endPos;
550   }
551
552  private:
553   ValueType setValue() {
554     value_ = static_cast<ValueType>(8 * outer_ + inner_ - position_);
555     return value_;
556   }
557
558   void reposition(size_t dest) {
559     outer_ = dest / 8;
560     block_ = folly::loadUnaligned<block_t>(start_ + outer_);
561     block_ &= ~((block_t(1) << (dest % 8)) - 1);
562   }
563
564   typedef uint64_t block_t;
565   const unsigned char* const forwardPointers_;
566   const unsigned char* const skipPointers_;
567   const unsigned char* const start_;
568   block_t block_;
569   size_t outer_;  // Outer offset: number of consumed bytes in upper.
570   size_t inner_;  // Inner offset: (bit) position in current block.
571   size_t position_;  // Index of current value (= #reads - 1).
572   ValueType value_;
573 };
574
575 }  // namespace detail
576
577 // If kUnchecked = true the caller must guarantee that all the
578 // operations return valid elements, i.e., they would never return
579 // false if checked.
580 template <class Encoder,
581           class Instructions = instructions::Default,
582           bool kUnchecked = false>
583 class EliasFanoReader {
584  public:
585   typedef Encoder EncoderType;
586   typedef typename Encoder::ValueType ValueType;
587
588   explicit EliasFanoReader(const EliasFanoCompressedList& list)
589       : size_(list.size),
590         lower_(list.lower),
591         upper_(list),
592         lowerMask_((ValueType(1) << list.numLowerBits) - 1),
593         numLowerBits_(list.numLowerBits) {
594     DCHECK(Instructions::supported());
595     // To avoid extra branching during skipTo() while reading
596     // upper sequence we need to know the last element.
597     // If kUnchecked == true, we do not check that skipTo() is called
598     // within the bounds, so we can avoid initializing lastValue_.
599     if (kUnchecked || UNLIKELY(list.size == 0)) {
600       lastValue_ = 0;
601       return;
602     }
603     ValueType lastUpperValue = 8 * list.upperSize() - size_;
604     auto it = list.upper + list.upperSize() - 1;
605     DCHECK_NE(*it, 0);
606     lastUpperValue -= 8 - folly::findLastSet(*it);
607     lastValue_ = readLowerPart(size_ - 1) | (lastUpperValue << numLowerBits_);
608   }
609
610   void reset() {
611     upper_.reset();
612     value_ = 0;
613   }
614
615   bool next() {
616     if (!kUnchecked && UNLIKELY(position() + 1 >= size_)) {
617       return setDone();
618     }
619     upper_.next();
620     value_ = readLowerPart(upper_.position()) |
621              (upper_.value() << numLowerBits_);
622     return true;
623   }
624
625   bool skip(size_t n) {
626     CHECK_GT(n, 0);
627
628     if (kUnchecked || LIKELY(position() + n < size_)) {
629       if (LIKELY(n < kLinearScanThreshold)) {
630         for (size_t i = 0; i < n; ++i) upper_.next();
631       } else {
632         upper_.skip(n);
633       }
634       value_ = readLowerPart(upper_.position()) |
635         (upper_.value() << numLowerBits_);
636       return true;
637     }
638
639     return setDone();
640   }
641
642   bool skipTo(ValueType value) {
643     DCHECK_GE(value, value_);
644     if (value <= value_) {
645       return true;
646     } else if (!kUnchecked && value > lastValue_) {
647       return setDone();
648     }
649
650     size_t upperValue = (value >> numLowerBits_);
651     size_t upperSkip = upperValue - upper_.value();
652     // The average density of ones in upper bits is 1/2.
653     // LIKELY here seems to make things worse, even for small skips.
654     if (upperSkip < 2 * kLinearScanThreshold) {
655       do {
656         upper_.next();
657       } while (UNLIKELY(upper_.value() < upperValue));
658     } else {
659       upper_.skipToNext(upperValue);
660     }
661
662     iterateTo(value);
663     return true;
664   }
665
666   bool jump(size_t n) {
667     if (LIKELY(n - 1 < size_)) {  // n > 0 && n <= size_
668       value_ = readLowerPart(n - 1) | (upper_.jump(n) << numLowerBits_);
669       return true;
670     } else if (n == 0) {
671       reset();
672       return true;
673     }
674     return setDone();
675   }
676
677   bool jumpTo(ValueType value) {
678     if (value <= 0) {
679       reset();
680       return true;
681     } else if (!kUnchecked && value > lastValue_) {
682       return setDone();
683     }
684
685     upper_.jumpToNext(value >> numLowerBits_);
686     iterateTo(value);
687     return true;
688   }
689
690   ValueType previousValue() const {
691     DCHECK_GT(position(), 0);
692     DCHECK_LT(position(), size());
693     return readLowerPart(upper_.position() - 1) |
694       (upper_.previousValue() << numLowerBits_);
695   }
696
697   size_t size() const { return size_; }
698
699   size_t position() const { return upper_.position(); }
700   ValueType value() const { return value_; }
701
702  private:
703   bool setDone() {
704     value_ = std::numeric_limits<ValueType>::max();
705     upper_.setDone(size_);
706     return false;
707   }
708
709   ValueType readLowerPart(size_t i) const {
710     DCHECK_LT(i, size_);
711     const size_t pos = i * numLowerBits_;
712     const unsigned char* ptr = lower_ + (pos / 8);
713     const uint64_t ptrv = folly::loadUnaligned<uint64_t>(ptr);
714     return lowerMask_ & (ptrv >> (pos % 8));
715   }
716
717   void iterateTo(ValueType value) {
718     while (true) {
719       value_ = readLowerPart(upper_.position()) |
720         (upper_.value() << numLowerBits_);
721       if (LIKELY(value_ >= value)) break;
722       upper_.next();
723     }
724   }
725
726   constexpr static size_t kLinearScanThreshold = 8;
727
728   size_t size_;
729   const uint8_t* lower_;
730   detail::UpperBitsReader<Encoder, Instructions> upper_;
731   const ValueType lowerMask_;
732   ValueType value_ = 0;
733   ValueType lastValue_;
734   uint8_t numLowerBits_;
735 };
736
737 }}  // namespaces
738
739 #endif  // FOLLY_EXPERIMENTAL_ELIAS_FANO_CODING_H