From: Philip Pronin Date: Sun, 3 Aug 2014 00:33:03 +0000 (-0700) Subject: streaming support for EF compression X-Git-Tag: v0.22.0~418 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=942204a40c737db166205064c6997d74c7c153fa;p=folly.git streaming support for EF compression Test Plan: fbconfig -r folly/experimental/test:eliasfano_test && fbmake opt -j32 Reviewed By: lucian@fb.com Subscribers: chaoyc, search-fbcode-diffs@, unicorn-diffs@ FB internal diff: D1474625 Tasks: 4828866 --- diff --git a/folly/experimental/EliasFanoCoding.h b/folly/experimental/EliasFanoCoding.h index dd83a617..c7406fd0 100644 --- a/folly/experimental/EliasFanoCoding.h +++ b/folly/experimental/EliasFanoCoding.h @@ -32,7 +32,6 @@ #error EliasFanoCoding.h requires x86_64 #endif -#include #include #include #include @@ -51,8 +50,7 @@ namespace folly { namespace compression { struct EliasFanoCompressedList { - EliasFanoCompressedList() - : size(0), numLowerBits(0) { } + EliasFanoCompressedList() { } void free() { ::free(const_cast(lower.data())); @@ -61,8 +59,8 @@ struct EliasFanoCompressedList { ::free(const_cast(forwardPointers.data())); } - size_t size; - uint8_t numLowerBits; + size_t size = 0; + uint8_t numLowerBits = 0; // WARNING: EliasFanoCompressedList has no ownership of // lower, upper, skipPointers and forwardPointers. @@ -106,30 +104,29 @@ struct EliasFanoEncoder { return folly::findLastSet(upperBound / size) - 1; } + // Requires: input range (begin, end) is sorted (encoding + // crashes if it's not). // WARNING: encode() mallocates lower, upper, skipPointers // and forwardPointers. As EliasFanoCompressedList has // no ownership of them, you need to call free() explicitly. - static void encode(const ValueType* list, size_t size, - EliasFanoCompressedList& result) { - encode(list, list + size, result); - } - - // Range (begin, end) should be sorted. template - static void encode(RandomAccessIterator begin, - RandomAccessIterator end, - EliasFanoCompressedList& result) { - CHECK(std::is_sorted(begin, end)); - - auto list = begin; - const size_t size = end - begin; + static EliasFanoCompressedList encode(RandomAccessIterator begin, + RandomAccessIterator end) { + if (begin == end) { + return EliasFanoCompressedList(); + } + EliasFanoEncoder encoder(end - begin, *(end - 1)); + for (; begin != end; ++begin) { + encoder.add(*begin); + } + return encoder.finish(); + } + EliasFanoEncoder(size_t size, ValueType upperBound) { if (size == 0) { - result = EliasFanoCompressedList(); return; } - const ValueType upperBound = list[size - 1]; uint8_t numLowerBits = defaultNumLowerBits(upperBound, size); // This is detail::writeBits56 limitation. @@ -144,14 +141,8 @@ struct EliasFanoEncoder { // *** Lower bits. const size_t lowerSize = (numLowerBits * size + 7) / 8; - unsigned char* lower = nullptr; if (lowerSize > 0) { // numLowerBits != 0 - lower = static_cast(calloc(lowerSize + 7, 1)); - const ValueType lowerMask = (ValueType(1) << numLowerBits) - 1; - for (size_t i = 0; i < size; ++i) { - const ValueType lowerBits = list[i] & lowerMask; - writeBits56(lower, i * numLowerBits, numLowerBits, lowerBits); - } + lower_ = static_cast(calloc(lowerSize + 7, 1)); } // *** Upper bits. @@ -161,22 +152,13 @@ struct EliasFanoEncoder { (upperBound >> numLowerBits) + // Number of 0-bits to be stored. size; // 1-bits. const size_t upperSize = (upperSizeBits + 7) / 8; - unsigned char* const upper = - static_cast(calloc(upperSize + 7, 1)); - for (size_t i = 0; i < size; ++i) { - const ValueType upperBits = list[i] >> numLowerBits; - const size_t pos = upperBits + i; // upperBits 0-bits and (i + 1) 1-bits. - upper[pos / 8] |= 1U << (pos % 8); - } + upper_ = static_cast(calloc(upperSize + 7, 1)); // *** Skip pointers. // Store (1-indexed) position of every skipQuantum-th // 0-bit in upper bits sequence. - SkipValueType* skipPointers = nullptr; size_t numSkipPointers = 0; /* static */ if (skipQuantum != 0) { - // Workaround to avoid 'division by zero' compile-time error. - constexpr size_t q = skipQuantum ?: 1; /* static */ if (kVersion > 0) { CHECK_LT(size, std::numeric_limits::max()); } else { @@ -186,33 +168,18 @@ struct EliasFanoEncoder { // more serialization-friendly way (upperSizeBits isn't known outside of // this function, unlike upperSize; thus numSkipPointers could easily be // deduced from upperSize). - numSkipPointers = (8 * upperSize - size) / q; - skipPointers = static_cast( + numSkipPointers = (8 * upperSize - size) / (skipQuantum ?: 1); + skipPointers_ = static_cast( numSkipPointers == 0 ? nullptr : calloc(numSkipPointers, sizeof(SkipValueType))); - - for (size_t i = 0, pos = 0; i < size; ++i) { - const ValueType upperBits = list[i] >> numLowerBits; - for (; (pos + 1) * q <= upperBits; ++pos) { - /* static */ if (kVersion > 0) { - // Since version 1, just the number of preceding 1-bits is stored. - skipPointers[pos] = i; - } else { - skipPointers[pos] = i + (pos + 1) * q; - } - } - } } // *** Forward pointers. // Store (1-indexed) position of every forwardQuantum-th // 1-bit in upper bits sequence. - SkipValueType* forwardPointers = nullptr; size_t numForwardPointers = 0; /* static */ if (forwardQuantum != 0) { - // Workaround to avoid 'division by zero' compile-time error. - constexpr size_t q = forwardQuantum ?: 1; /* static */ if (kVersion > 0) { CHECK_LT(upperBound >> numLowerBits, std::numeric_limits::max()); @@ -220,34 +187,74 @@ struct EliasFanoEncoder { CHECK_LT(upperSizeBits, std::numeric_limits::max()); } - numForwardPointers = size / q; - forwardPointers = static_cast( + // '?: 1' is a workaround for false 'division by zero' compile-time error. + numForwardPointers = size / (forwardQuantum ?: 1); + forwardPointers_ = static_cast( numForwardPointers == 0 ? nullptr : malloc(numForwardPointers * sizeof(SkipValueType))); + } + + // *** Result. + result_.size = size; + result_.numLowerBits = numLowerBits; + result_.lower.reset(lower_, lowerSize); + result_.upper.reset(upper_, upperSize); + result_.skipPointers.reset( + reinterpret_cast(skipPointers_), + numSkipPointers * sizeof(SkipValueType)); + result_.forwardPointers.reset( + reinterpret_cast(forwardPointers_), + numForwardPointers * sizeof(SkipValueType)); + } + + void add(ValueType value) { + CHECK_GE(value, lastValue_); - for (size_t i = q - 1, pos = 0; i < size; i += q, ++pos) { - const ValueType upperBits = list[i] >> numLowerBits; + const auto numLowerBits = result_.numLowerBits; + const ValueType upperBits = value >> numLowerBits; + + // Upper sequence consists of upperBits 0-bits and (size_ + 1) 1-bits. + const size_t pos = upperBits + size_; + upper_[pos / 8] |= 1U << (pos % 8); + // Append numLowerBits bits to lower sequence. + if (numLowerBits != 0) { + const ValueType lowerBits = value & ((ValueType(1) << numLowerBits) - 1); + writeBits56(lower_, size_ * numLowerBits, numLowerBits, lowerBits); + } + + /* static */ if (skipQuantum != 0) { + while ((skipPointersSize_ + 1) * skipQuantum <= upperBits) { + /* static */ if (kVersion > 0) { + // Since version 1, just the number of preceding 1-bits is stored. + skipPointers_[skipPointersSize_] = size_; + } else { + skipPointers_[skipPointersSize_] = + size_ + (skipPointersSize_ + 1) * skipQuantum; + } + ++skipPointersSize_; + } + } + + /* static */ if (forwardQuantum != 0) { + if ((size_ + 1) % forwardQuantum == 0) { + const auto pos = size_ / forwardQuantum; /* static */ if (kVersion > 0) { // Since version 1, just the number of preceding 0-bits is stored. - forwardPointers[pos] = upperBits; + forwardPointers_[pos] = upperBits; } else { - forwardPointers[pos] = upperBits + i + 1; + forwardPointers_[pos] = upperBits + size_ + 1; } } } - // *** Result. - result.size = size; - result.numLowerBits = numLowerBits; - result.lower.reset(lower, lowerSize); - result.upper.reset(upper, upperSize); - result.skipPointers.reset( - reinterpret_cast(skipPointers), - numSkipPointers * sizeof(SkipValueType)); - result.forwardPointers.reset( - reinterpret_cast(forwardPointers), - numForwardPointers * sizeof(SkipValueType)); + lastValue_ = value; + ++size_; + } + + const EliasFanoCompressedList& finish() const { + CHECK_EQ(size_, result_.size); + return result_; } private: @@ -261,6 +268,17 @@ struct EliasFanoEncoder { ptrv |= value << (pos % 8); folly::storeUnaligned(ptr, ptrv); } + + unsigned char* lower_ = nullptr; + unsigned char* upper_ = nullptr; + SkipValueType* skipPointers_ = nullptr; + SkipValueType* forwardPointers_ = nullptr; + + ValueType lastValue_ = 0; + size_t size_ = 0; + size_t skipPointersSize_ = 0; + + EliasFanoCompressedList result_; }; // NOTE: It's recommended to compile EF coding with -msse4.2, starting diff --git a/folly/experimental/test/CodingTestUtils.h b/folly/experimental/test/CodingTestUtils.h index fb01db8c..e1ed84a6 100644 --- a/folly/experimental/test/CodingTestUtils.h +++ b/folly/experimental/test/CodingTestUtils.h @@ -177,9 +177,8 @@ void testGoTo(const std::vector& data, const List& list) { template void testEmpty() { - typename Encoder::CompressedList list; const typename Encoder::ValueType* const data = nullptr; - Encoder::encode(data, 0, list); + auto list = Encoder::encode(data, data); { Reader reader(list); EXPECT_FALSE(reader.next()); @@ -198,8 +197,7 @@ void testEmpty() { template void testAll(const std::vector& data) { - typename Encoder::CompressedList list; - Encoder::encode(data.begin(), data.end(), list); + auto list = Encoder::encode(data.begin(), data.end()); testNext(data, list); testSkip(data, list); testSkipTo(data, list); diff --git a/folly/experimental/test/EliasFanoCodingTest.cpp b/folly/experimental/test/EliasFanoCodingTest.cpp index be0ef4c5..91da7787 100644 --- a/folly/experimental/test/EliasFanoCodingTest.cpp +++ b/folly/experimental/test/EliasFanoCodingTest.cpp @@ -82,6 +82,9 @@ typedef EliasFanoReader Reader; std::vector data; std::vector order; +std::vector encodeSmallData; +std::vector encodeLargeData; + typename Encoder::CompressedList list; void init() { @@ -89,7 +92,7 @@ void init() { data = generateRandomList(100 * 1000, 10 * 1000 * 1000, gen); //data = loadList("/home/philipp/pl_test_dump.txt"); - Encoder::encode(data.data(), data.size(), bm::list); + list = Encoder::encode(data.begin(), data.end()); order.clear(); order.reserve(data.size()); @@ -97,6 +100,9 @@ void init() { order.push_back(i); } std::shuffle(order.begin(), order.end(), gen); + + encodeSmallData = generateRandomList(10, 100 * 1000, gen); + encodeLargeData = generateRandomList(1000 * 1000, 100 * 1000 * 1000, gen); } void free() { @@ -133,6 +139,8 @@ BENCHMARK(SkipTo1_SkipQ128_1M) { bmSkipTo(bm::list, bm::data, 1, bm::k1M); } +BENCHMARK_DRAW_LINE(); + BENCHMARK(SkipTo10_SkipQ128_1M) { bmSkipTo(bm::list, bm::data, 10, bm::k1M); } @@ -145,8 +153,22 @@ BENCHMARK(SkipTo1000_SkipQ128_1M) { bmSkipTo(bm::list, bm::data, 1000, bm::k1M); } +BENCHMARK_DRAW_LINE(); + +BENCHMARK(Encode_10) { + auto list = bm::Encoder::encode(bm::encodeSmallData.begin(), + bm::encodeSmallData.end()); + list.free(); +} + +BENCHMARK(Encode_1M) { + auto list = bm::Encoder::encode(bm::encodeLargeData.begin(), + bm::encodeLargeData.end()); + list.free(); +} + #if 0 -Intel Xeon CPU E5-2660 @ 2.7GHz (turbo on), using instructions::Fast. +Intel(R) Xeon(R) CPU E5-2660 @ 2.7GHz (turbo on), using instructions::Fast. ============================================================================ folly/experimental/test/EliasFanoCodingTest.cpp relative time/iter iters/s @@ -157,10 +179,14 @@ Skip10_ForwarQ128_1M 13.69ms 73.03 Skip100_ForwardQ128_1M 26.76ms 37.37 Skip1000_ForwardQ128_1M 20.66ms 48.40 GoTo_ForwardQ128_1M 43.75ms 22.86 +---------------------------------------------------------------------------- SkipTo1_SkipQ128_1M 9.74ms 102.70 SkipTo10_SkipQ128_1M 30.62ms 32.66 SkipTo100_SkipQ128_1M 37.70ms 26.53 SkipTo1000_SkipQ128_1M 31.14ms 32.11 +---------------------------------------------------------------------------- +Encode_10 208.16ns 4.80M +Encode_1M 8.90ms 112.37 ============================================================================ #endif