Move folly::compression::Instructions to a separate file
[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/Instructions.h>
38 #include <folly/experimental/Select64.h>
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 namespace detail {
333
334 template <class Encoder, class Instructions>
335 class UpperBitsReader {
336   typedef typename Encoder::SkipValueType SkipValueType;
337  public:
338   typedef typename Encoder::ValueType ValueType;
339
340   explicit UpperBitsReader(const EliasFanoCompressedList& list)
341     : forwardPointers_(list.forwardPointers),
342       skipPointers_(list.skipPointers),
343       start_(list.upper) {
344     reset();
345   }
346
347   void reset() {
348     block_ = start_ != nullptr ? folly::loadUnaligned<block_t>(start_) : 0;
349     outer_ = 0;
350     inner_ = -1;
351     position_ = -1;
352     value_ = 0;
353   }
354
355   size_t position() const { return position_; }
356   ValueType value() const { return value_; }
357
358   ValueType next() {
359     // Skip to the first non-zero block.
360     while (block_ == 0) {
361       outer_ += sizeof(block_t);
362       block_ = folly::loadUnaligned<block_t>(start_ + outer_);
363     }
364
365     ++position_;
366     inner_ = Instructions::ctz(block_);
367     block_ = Instructions::blsr(block_);
368
369     return setValue();
370   }
371
372   ValueType skip(size_t n) {
373     DCHECK_GT(n, 0);
374
375     position_ += n;  // n 1-bits will be read.
376
377     // Use forward pointer.
378     if (Encoder::forwardQuantum > 0 && n > Encoder::forwardQuantum) {
379       // Workaround to avoid 'division by zero' compile-time error.
380       constexpr size_t q = Encoder::forwardQuantum ?: 1;
381
382       const size_t steps = position_ / q;
383       const size_t dest =
384         folly::loadUnaligned<SkipValueType>(
385             forwardPointers_ + (steps - 1) * sizeof(SkipValueType));
386
387       reposition(dest + steps * q);
388       n = position_ + 1 - steps * q;  // n is > 0.
389       // Correct inner_ will be set at the end.
390     }
391
392     size_t cnt;
393     // Find necessary block.
394     while ((cnt = Instructions::popcount(block_)) < n) {
395       n -= cnt;
396       outer_ += sizeof(block_t);
397       block_ = folly::loadUnaligned<block_t>(start_ + outer_);
398     }
399
400     // Skip to the n-th one in the block.
401     DCHECK_GT(n, 0);
402     inner_ = select64<Instructions>(block_, n - 1);
403     block_ &= (block_t(-1) << inner_) << 1;
404
405     return setValue();
406   }
407
408   // Skip to the first element that is >= v and located *after* the current
409   // one (so even if current value equals v, position will be increased by 1).
410   ValueType skipToNext(ValueType v) {
411     DCHECK_GE(v, value_);
412
413     // Use skip pointer.
414     if (Encoder::skipQuantum > 0 && v >= value_ + Encoder::skipQuantum) {
415       // Workaround to avoid 'division by zero' compile-time error.
416       constexpr size_t q = Encoder::skipQuantum ?: 1;
417
418       const size_t steps = v / q;
419       const size_t dest =
420         folly::loadUnaligned<SkipValueType>(
421             skipPointers_ + (steps - 1) * sizeof(SkipValueType));
422
423       reposition(dest + q * steps);
424       position_ = dest - 1;
425
426       // Correct inner_ and value_ will be set during the next()
427       // call at the end.
428
429       // NOTE: Corresponding block of lower bits sequence may be
430       // prefetched here (via __builtin_prefetch), but experiments
431       // didn't show any significant improvements.
432     }
433
434     // Skip by blocks.
435     size_t cnt;
436     size_t skip = v - (8 * outer_ - position_ - 1);
437
438     constexpr size_t kBitsPerBlock = 8 * sizeof(block_t);
439     while ((cnt = Instructions::popcount(~block_)) < skip) {
440       skip -= cnt;
441       position_ += kBitsPerBlock - cnt;
442       outer_ += sizeof(block_t);
443       block_ = folly::loadUnaligned<block_t>(start_ + outer_);
444     }
445
446     if (LIKELY(skip)) {
447       auto inner = select64<Instructions>(~block_, skip - 1);
448       position_ += inner - skip + 1;
449       block_ &= block_t(-1) << inner;
450     }
451
452     next();
453     return value_;
454   }
455
456   ValueType jump(size_t n) {
457     if (Encoder::forwardQuantum == 0 || n <= Encoder::forwardQuantum) {
458       reset();
459     } else {
460       position_ = -1;  // Avoid reading the head, skip() will reposition.
461     }
462     return skip(n);
463   }
464
465   ValueType jumpToNext(ValueType v) {
466     if (Encoder::skipQuantum == 0 || v < Encoder::skipQuantum) {
467       reset();
468     } else {
469       value_ = 0;  // Avoid reading the head, skipToNext() will reposition.
470     }
471     return skipToNext(v);
472   }
473
474   ValueType previousValue() const {
475     DCHECK_NE(position(), -1);
476     DCHECK_GT(position(), 0);
477
478     size_t outer = outer_;
479     block_t block = folly::loadUnaligned<block_t>(start_ + outer);
480     block &= (block_t(1) << inner_) - 1;
481
482     while (UNLIKELY(block == 0)) {
483       DCHECK_GE(outer, sizeof(block_t));
484       outer -= sizeof(block_t);
485       block = folly::loadUnaligned<block_t>(start_ + outer);
486     }
487
488     auto inner = 8 * sizeof(block_t) - 1 - Instructions::clz(block);
489     return static_cast<ValueType>(8 * outer + inner - (position_ - 1));
490   }
491
492   void setDone(size_t endPos) {
493     position_ = endPos;
494   }
495
496  private:
497   ValueType setValue() {
498     value_ = static_cast<ValueType>(8 * outer_ + inner_ - position_);
499     return value_;
500   }
501
502   void reposition(size_t dest) {
503     outer_ = dest / 8;
504     block_ = folly::loadUnaligned<block_t>(start_ + outer_);
505     block_ &= ~((block_t(1) << (dest % 8)) - 1);
506   }
507
508   typedef uint64_t block_t;
509   const unsigned char* const forwardPointers_;
510   const unsigned char* const skipPointers_;
511   const unsigned char* const start_;
512   block_t block_;
513   size_t outer_;  // Outer offset: number of consumed bytes in upper.
514   size_t inner_;  // Inner offset: (bit) position in current block.
515   size_t position_;  // Index of current value (= #reads - 1).
516   ValueType value_;
517 };
518
519 }  // namespace detail
520
521 // If kUnchecked = true the caller must guarantee that all the
522 // operations return valid elements, i.e., they would never return
523 // false if checked.
524 template <class Encoder,
525           class Instructions = instructions::Default,
526           bool kUnchecked = false>
527 class EliasFanoReader {
528  public:
529   typedef Encoder EncoderType;
530   typedef typename Encoder::ValueType ValueType;
531
532   explicit EliasFanoReader(const EliasFanoCompressedList& list)
533       : size_(list.size),
534         lower_(list.lower),
535         upper_(list),
536         lowerMask_((ValueType(1) << list.numLowerBits) - 1),
537         numLowerBits_(list.numLowerBits) {
538     DCHECK(Instructions::supported());
539     // To avoid extra branching during skipTo() while reading
540     // upper sequence we need to know the last element.
541     // If kUnchecked == true, we do not check that skipTo() is called
542     // within the bounds, so we can avoid initializing lastValue_.
543     if (kUnchecked || UNLIKELY(list.size == 0)) {
544       lastValue_ = 0;
545       return;
546     }
547     ValueType lastUpperValue = 8 * list.upperSize() - size_;
548     auto it = list.upper + list.upperSize() - 1;
549     DCHECK_NE(*it, 0);
550     lastUpperValue -= 8 - folly::findLastSet(*it);
551     lastValue_ = readLowerPart(size_ - 1) | (lastUpperValue << numLowerBits_);
552   }
553
554   void reset() {
555     upper_.reset();
556     value_ = 0;
557   }
558
559   bool next() {
560     if (!kUnchecked && UNLIKELY(position() + 1 >= size_)) {
561       return setDone();
562     }
563     upper_.next();
564     value_ = readLowerPart(upper_.position()) |
565              (upper_.value() << numLowerBits_);
566     return true;
567   }
568
569   bool skip(size_t n) {
570     CHECK_GT(n, 0);
571
572     if (kUnchecked || LIKELY(position() + n < size_)) {
573       if (LIKELY(n < kLinearScanThreshold)) {
574         for (size_t i = 0; i < n; ++i) upper_.next();
575       } else {
576         upper_.skip(n);
577       }
578       value_ = readLowerPart(upper_.position()) |
579         (upper_.value() << numLowerBits_);
580       return true;
581     }
582
583     return setDone();
584   }
585
586   bool skipTo(ValueType value) {
587     DCHECK_GE(value, value_);
588     if (value <= value_) {
589       return true;
590     } else if (!kUnchecked && value > lastValue_) {
591       return setDone();
592     }
593
594     size_t upperValue = (value >> numLowerBits_);
595     size_t upperSkip = upperValue - upper_.value();
596     // The average density of ones in upper bits is 1/2.
597     // LIKELY here seems to make things worse, even for small skips.
598     if (upperSkip < 2 * kLinearScanThreshold) {
599       do {
600         upper_.next();
601       } while (UNLIKELY(upper_.value() < upperValue));
602     } else {
603       upper_.skipToNext(upperValue);
604     }
605
606     iterateTo(value);
607     return true;
608   }
609
610   bool jump(size_t n) {
611     if (LIKELY(n - 1 < size_)) {  // n > 0 && n <= size_
612       value_ = readLowerPart(n - 1) | (upper_.jump(n) << numLowerBits_);
613       return true;
614     } else if (n == 0) {
615       reset();
616       return true;
617     }
618     return setDone();
619   }
620
621   bool jumpTo(ValueType value) {
622     if (value <= 0) {
623       reset();
624       return true;
625     } else if (!kUnchecked && value > lastValue_) {
626       return setDone();
627     }
628
629     upper_.jumpToNext(value >> numLowerBits_);
630     iterateTo(value);
631     return true;
632   }
633
634   ValueType previousValue() const {
635     DCHECK_GT(position(), 0);
636     DCHECK_LT(position(), size());
637     return readLowerPart(upper_.position() - 1) |
638       (upper_.previousValue() << numLowerBits_);
639   }
640
641   size_t size() const { return size_; }
642
643   size_t position() const { return upper_.position(); }
644   ValueType value() const { return value_; }
645
646  private:
647   bool setDone() {
648     value_ = std::numeric_limits<ValueType>::max();
649     upper_.setDone(size_);
650     return false;
651   }
652
653   ValueType readLowerPart(size_t i) const {
654     DCHECK_LT(i, size_);
655     const size_t pos = i * numLowerBits_;
656     const unsigned char* ptr = lower_ + (pos / 8);
657     const uint64_t ptrv = folly::loadUnaligned<uint64_t>(ptr);
658     return lowerMask_ & (ptrv >> (pos % 8));
659   }
660
661   void iterateTo(ValueType value) {
662     while (true) {
663       value_ = readLowerPart(upper_.position()) |
664         (upper_.value() << numLowerBits_);
665       if (LIKELY(value_ >= value)) break;
666       upper_.next();
667     }
668   }
669
670   constexpr static size_t kLinearScanThreshold = 8;
671
672   size_t size_;
673   const uint8_t* lower_;
674   detail::UpperBitsReader<Encoder, Instructions> upper_;
675   const ValueType lowerMask_;
676   ValueType value_ = 0;
677   ValueType lastValue_;
678   uint8_t numLowerBits_;
679 };
680
681 }}  // namespaces
682
683 #endif  // FOLLY_EXPERIMENTAL_ELIAS_FANO_CODING_H