2 * Copyright 2015 Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #ifndef FOLLY_EXPERIMENTAL_BIT_VECTOR_CODING_H
18 #define FOLLY_EXPERIMENTAL_BIT_VECTOR_CODING_H
22 #include <type_traits>
24 #include <folly/Bits.h>
25 #include <folly/Likely.h>
26 #include <folly/Portability.h>
27 #include <folly/Range.h>
28 #include <folly/experimental/Bits.h>
29 #include <folly/experimental/Instructions.h>
30 #include <folly/experimental/Select64.h>
31 #include <glog/logging.h>
34 #error BitVectorCoding.h requires GCC
38 #error BitVectorCoding.h requires x86_64
41 #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
42 #error BitVectorCoding.h requires little endianness
45 namespace folly { namespace compression {
47 template <class Pointer>
48 struct BitVectorCompressedListBase {
49 BitVectorCompressedListBase() = default;
51 template <class OtherPointer>
52 BitVectorCompressedListBase(
53 const BitVectorCompressedListBase<OtherPointer>& other)
55 upperBound(other.upperBound),
57 bits(reinterpret_cast<Pointer>(other.bits)),
58 skipPointers(reinterpret_cast<Pointer>(other.skipPointers)),
59 forwardPointers(reinterpret_cast<Pointer>(other.forwardPointers)) {}
61 template <class T = Pointer>
62 auto free() -> decltype(::free(T(nullptr))) {
63 return ::free(data.data());
67 size_t upperBound = 0;
69 folly::Range<Pointer> data;
71 Pointer bits = nullptr;
72 Pointer skipPointers = nullptr;
73 Pointer forwardPointers = nullptr;
76 typedef BitVectorCompressedListBase<const uint8_t*> BitVectorCompressedList;
77 typedef BitVectorCompressedListBase<uint8_t*> MutableBitVectorCompressedList;
79 template <class Value,
81 size_t kSkipQuantum = 0,
82 size_t kForwardQuantum = 0>
83 struct BitVectorEncoder {
84 static_assert(std::is_integral<Value>::value &&
85 std::is_unsigned<Value>::value,
86 "Value should be unsigned integral");
88 typedef BitVectorCompressedList CompressedList;
89 typedef MutableBitVectorCompressedList MutableCompressedList;
91 typedef Value ValueType;
92 typedef SkipValue SkipValueType;
95 static constexpr size_t skipQuantum = kSkipQuantum;
96 static constexpr size_t forwardQuantum = kForwardQuantum;
98 template <class RandomAccessIterator>
99 static MutableCompressedList encode(RandomAccessIterator begin,
100 RandomAccessIterator end) {
102 return MutableCompressedList();
104 BitVectorEncoder encoder(end - begin, *(end - 1));
105 for (; begin != end; ++begin) {
108 return encoder.finish();
111 explicit BitVectorEncoder(const MutableCompressedList& result)
112 : bits_(result.bits),
113 skipPointers_(result.skipPointers),
114 forwardPointers_(result.forwardPointers),
116 memset(result.data.data(), 0, result.data.size());
119 BitVectorEncoder(size_t size, ValueType upperBound)
121 Layout::fromUpperBoundAndSize(upperBound, size).allocList()) {}
123 void add(ValueType value) {
124 CHECK_GE(value, lastValue_);
125 auto block = bits_ + (value / 64) * sizeof(uint64_t);
126 size_t inner = value % 64;
127 folly::Bits<folly::Unaligned<uint64_t>>::set(
128 reinterpret_cast<folly::Unaligned<uint64_t>*>(block), inner);
130 if (skipQuantum != 0) {
131 size_t nextSkipPointerSize = value / (skipQuantum ?: 1);
132 while (skipPointersSize_ < nextSkipPointerSize) {
133 auto pos = skipPointersSize_++;
134 folly::storeUnaligned<SkipValueType>(
135 skipPointers_ + pos * sizeof(SkipValueType), size_);
139 if (forwardQuantum != 0) {
140 if (size_ != 0 && (size_ % (forwardQuantum ?: 1) == 0)) {
141 const auto pos = size_ / (forwardQuantum ?: 1) - 1;
142 folly::storeUnaligned<SkipValueType>(
143 forwardPointers_ + pos * sizeof(SkipValueType), value);
151 const MutableCompressedList& finish() const {
152 CHECK_EQ(size_, result_.size);
153 // TODO(ott): Relax this assumption.
154 CHECK_EQ(result_.upperBound, lastValue_);
159 uint8_t* const bits_ = nullptr;
160 uint8_t* const skipPointers_ = nullptr;
161 uint8_t* const forwardPointers_ = nullptr;
163 ValueType lastValue_ = 0;
165 size_t skipPointersSize_ = 0;
167 MutableCompressedList result_;
170 template <class Value,
173 size_t kForwardQuantum>
174 struct BitVectorEncoder<Value, SkipValue, kSkipQuantum, kForwardQuantum>::
176 static Layout fromUpperBoundAndSize(size_t upperBound, size_t size) {
179 layout.upperBound = upperBound;
181 size_t bitVectorSizeInBytes = (upperBound / 8) + 1;
182 layout.bits = bitVectorSizeInBytes;
184 if (skipQuantum != 0) {
185 size_t numSkipPointers = upperBound / (skipQuantum ?: 1);
186 layout.skipPointers = numSkipPointers * sizeof(SkipValueType);
188 if (forwardQuantum != 0) {
189 size_t numForwardPointers = size / (forwardQuantum ?: 1);
190 layout.forwardPointers = numForwardPointers * sizeof(SkipValueType);
193 CHECK_LT(size, std::numeric_limits<SkipValueType>::max());
198 size_t bytes() const { return bits + skipPointers + forwardPointers; }
200 template <class Range>
201 BitVectorCompressedListBase<typename Range::iterator> openList(
203 BitVectorCompressedListBase<typename Range::iterator> result;
205 result.upperBound = upperBound;
206 result.data = buf.subpiece(0, bytes());
207 auto advance = [&](size_t n) {
208 auto begin = buf.data();
213 result.bits = advance(bits);
214 result.skipPointers = advance(skipPointers);
215 result.forwardPointers = advance(forwardPointers);
216 CHECK_EQ(buf.data() - result.data.data(), bytes());
221 MutableCompressedList allocList() const {
222 uint8_t* buf = nullptr;
224 buf = static_cast<uint8_t*>(malloc(bytes() + 7));
226 folly::MutableByteRange bufRange(buf, bytes());
227 return openList(bufRange);
231 size_t upperBound = 0;
235 size_t skipPointers = 0;
236 size_t forwardPointers = 0;
239 template <class Encoder,
240 class Instructions = instructions::Default,
241 bool kUnchecked = false>
242 class BitVectorReader {
244 typedef Encoder EncoderType;
245 typedef typename Encoder::ValueType ValueType;
246 typedef typename Encoder::SkipValueType SkipValueType;
248 explicit BitVectorReader(const typename Encoder::CompressedList& list)
251 skipPointers_(list.skipPointers),
252 forwardPointers_(list.forwardPointers) {
255 if (kUnchecked || UNLIKELY(list.size == 0)) {
260 upperBound_ = list.upperBound;
264 block_ = (bits_ != nullptr) ? folly::loadUnaligned<uint64_t>(bits_) : 0;
272 if (!kUnchecked && UNLIKELY(position() + 1 >= size_)) {
276 while (block_ == 0) {
277 outer_ += sizeof(uint64_t);
278 block_ = folly::loadUnaligned<uint64_t>(bits_ + outer_);
282 inner_ = Instructions::ctz(block_);
283 block_ = Instructions::blsr(block_);
288 bool skip(size_t n) {
291 if (!kUnchecked && position() + n >= size_) {
294 // Small skip optimization.
295 if (LIKELY(n < kLinearScanThreshold)) {
296 for (size_t i = 0; i < n; ++i) {
304 // Use forward pointer.
305 if (Encoder::forwardQuantum > 0 && n > Encoder::forwardQuantum) {
306 // Workaround to avoid 'division by zero' compile-time error.
307 constexpr size_t q = Encoder::forwardQuantum ?: 1;
309 const size_t steps = position_ / q;
310 const size_t dest = folly::loadUnaligned<SkipValueType>(
311 forwardPointers_ + (steps - 1) * sizeof(SkipValueType));
314 n = position_ + 1 - steps * q;
315 // Correct inner_ will be set at the end.
319 // Find necessary block.
320 while ((cnt = Instructions::popcount(block_)) < n) {
322 outer_ += sizeof(uint64_t);
323 block_ = folly::loadUnaligned<uint64_t>(bits_ + outer_);
326 // Skip to the n-th one in the block.
328 inner_ = select64<Instructions>(block_, n - 1);
329 block_ &= (uint64_t(-1) << inner_) << 1;
334 bool skipTo(ValueType v) {
335 DCHECK_GE(v, value_);
338 } else if (!kUnchecked && v > upperBound_) {
342 // Small skip optimization.
343 if (v - value_ < kLinearScanThreshold) {
346 } while (value() < v);
351 if (Encoder::skipQuantum > 0 && v - value_ > Encoder::skipQuantum) {
352 size_t q = v / Encoder::skipQuantum;
353 position_ = folly::loadUnaligned<SkipValueType>(
354 skipPointers_ + (q - 1) * sizeof(SkipValueType)) - 1;
356 reposition(q * Encoder::skipQuantum);
360 size_t outer = v / 64 * 8;
362 while (outer_ < outer) {
363 position_ += Instructions::popcount(block_);
364 outer_ += sizeof(uint64_t);
365 block_ = folly::loadUnaligned<uint64_t>(bits_ + outer_);
368 DCHECK_EQ(outer_, outer);
369 uint64_t mask = ~((uint64_t(1) << (v % 64)) - 1);
370 position_ += Instructions::popcount(block_ & ~mask) + 1;
373 while (block_ == 0) {
374 outer_ += sizeof(uint64_t);
375 block_ = folly::loadUnaligned<uint64_t>(bits_ + outer_);
378 inner_ = Instructions::ctz(block_);
379 block_ = Instructions::blsr(block_);
385 size_t size() const { return size_; }
387 size_t position() const { return position_; }
388 ValueType value() const { return value_; }
390 bool jump(size_t n) {
399 bool jumpTo(ValueType v) {
405 value_ = std::numeric_limits<ValueType>::max();
412 value_ = static_cast<ValueType>(8 * outer_ + inner_);
416 void reposition(size_t dest) {
417 outer_ = dest / 64 * 8;
418 // We maintain the invariant that outer_ is divisible by 8.
419 block_ = folly::loadUnaligned<uint64_t>(bits_ + outer_);
420 block_ &= ~((uint64_t(1) << (dest % 64)) - 1);
423 constexpr static size_t kLinearScanThreshold = 4;
429 ValueType value_ = 0;
432 ValueType upperBound_;
433 const uint8_t* const bits_;
434 const uint8_t* const skipPointers_;
435 const uint8_t* const forwardPointers_;
440 #endif // FOLLY_EXPERIMENTAL_BIT_VECTOR_CODING_H