blob: 140525b676d52da1ca561be4c03bf6213463d55c [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#pragma once
#include <cstddef>
#include <cstdint>
#include <utility>
namespace impala {
/// Utilities for manipulating bit-packed values. Bit-packing is a technique for
/// compressing integer values that do not use the full range of the integer type.
/// E.g. an array of uint32_t values with range [0, 31] only uses the lower 5 bits
/// of every uint32_t value, or an array of 0/1 booleans only uses the lowest bit
/// of each integer.
///
/// Bit-packing always has a "bit width" parameter that determines the range of
/// representable unsigned values: [0, 2^bit_width - 1]. The packed representation
/// is logically the concatenatation of the lower bits of the input values (in
/// little-endian order). E.g. the values 1, 2, 3, 4 packed with bit width 4 results
/// in the two output bytes: [ 0 0 1 0 | 0 0 0 1 ] [ 0 1 0 0 | 0 0 1 1 ]
/// 2 1 4 3
///
/// Packed values can be split across words, e.g. packing 1, 17 with bit_width 5 results
/// in the two output bytes: [ 0 0 1 | 0 0 0 0 1 ] [ x x x x x x | 1 0 ]
/// lower bits of 17--^ 1 next value ^--upper bits of 17
///
/// Bit widths from 0 to 64 are supported (0 bit width means that every value is 0).
/// The batched unpacking functions operate on batches of 32 values. This batch size
/// is convenient because for every supported bit width, the end of a 32 value batch
/// falls on a byte boundary. It is also large enough to amortise loop overheads.
class BitPacking {
public:
static constexpr int MAX_BITWIDTH = sizeof(uint64_t) * 8;
static constexpr int MAX_DICT_BITWIDTH = sizeof(uint32_t) * 8;
/// Unpack bit-packed values with 'bit_width' from 'in' to 'out'. Keeps unpacking until
/// either all 'in_bytes' are read or 'num_values' values are unpacked. 'out' must have
/// enough space for 'num_values'. 0 <= 'bit_width' <= 64 and 'bit_width' <= # of bits
/// in OutType. 'in' must point to 'in_bytes' of addressable memory.
///
/// Returns a pointer to the byte after the last byte of 'in' that was read and also the
/// number of values that were read. If the caller wants to continue reading packed
/// values after the last one returned, it must ensure that the next value to unpack
/// starts at a byte boundary. This is true if 'num_values' is a multiple of 32, or
/// more generally if (bit_width * num_values) % 8 == 0.
template <typename OutType>
static std::pair<const uint8_t*, int64_t> UnpackValues(int bit_width,
const uint8_t* __restrict__ in, int64_t in_bytes, int64_t num_values,
OutType* __restrict__ out);
/// Same as above, templated by BIT_WIDTH.
template <typename OutType, int BIT_WIDTH>
static std::pair<const uint8_t*, int64_t> UnpackValues(const uint8_t* __restrict__ in,
int64_t in_bytes, int64_t num_values, OutType* __restrict__ out);
/// Unpack values as above, treating them as unsigned integers, and decode them
/// using the provided dict. Writes them to 'out' with a stride of 'stride' bytes.
/// Sets 'decode_error' to true if one of the packed values was greater than 'dict_len'.
/// Does not modify 'decode_error' on success.
template <typename OutType>
static std::pair<const uint8_t*, int64_t> UnpackAndDecodeValues(int bit_width,
const uint8_t* __restrict__ in, int64_t in_bytes, OutType* __restrict__ dict,
int64_t dict_len, int64_t num_values, OutType* __restrict__ out, int64_t stride,
bool* __restrict__ decode_error);
/// Same as above, templated by BIT_WIDTH.
template <typename OutType, int BIT_WIDTH>
static std::pair<const uint8_t*, int64_t> UnpackAndDecodeValues(
const uint8_t* __restrict__ in, int64_t in_bytes, OutType* __restrict__ dict,
int64_t dict_len, int64_t num_values, OutType* __restrict__ out, int64_t stride,
bool* __restrict__ decode_error);
/// Unpack exactly 32 values of 'bit_width' from 'in' to 'out'. 'in' must point to
/// 'in_bytes' of addressable memory, and 'in_bytes' must be at least
/// (32 * bit_width / 8). 'out' must have space for 32 OutType values.
/// 0 <= 'bit_width' <= 64 and 'bit_width' <= # of bits in OutType.
template <typename OutType>
static const uint8_t* Unpack32Values(int bit_width, const uint8_t* __restrict__ in,
int64_t in_bytes, OutType* __restrict__ out);
/// Same as Unpack32Values() but templated by BIT_WIDTH.
template <typename OutType, int BIT_WIDTH>
static const uint8_t* Unpack32Values(
const uint8_t* __restrict__ in, int64_t in_bytes, OutType* __restrict__ out);
/// Same as Unpack32Values() with dictionary decoding.
template <typename OutType>
static const uint8_t* UnpackAndDecode32Values(int bit_width,
const uint8_t* __restrict__ in, int64_t in_bytes, OutType* __restrict__ dict,
int64_t dict_len, OutType* __restrict__ out, int64_t stride,
bool* __restrict__ decode_error);
/// Same as UnpackAndDecode32Values() but templated by BIT_WIDTH.
template <typename OutType, int BIT_WIDTH>
static const uint8_t* UnpackAndDecode32Values(const uint8_t* __restrict__ in,
int64_t in_bytes, OutType* __restrict__ dict, int64_t dict_len,
OutType* __restrict__ out, int64_t stride, bool* __restrict__ decode_error);
/// Unpacks 'num_values' values with the given BIT_WIDTH from 'in' to 'out'.
/// 'num_values' must be at most 31. 'in' must point to 'in_bytes' of addressable
/// memory, and 'in_bytes' must be at least ceil(num_values * bit_width / 8).
/// 'out' must have space for 'num_values' OutType values.
/// 0 <= 'bit_width' <= 64 and 'bit_width' <= # of bits in OutType.
template <typename OutType, int BIT_WIDTH>
static const uint8_t* UnpackUpTo31Values(const uint8_t* __restrict__ in,
int64_t in_bytes, int num_values, OutType* __restrict__ out);
/// Same as UnpackUpTo31Values() with dictionary decoding.
template <typename OutType, int BIT_WIDTH>
static const uint8_t* UnpackAndDecodeUpTo31Values(const uint8_t* __restrict__ in,
int64_t in_bytes, OutType* __restrict__ dict, int64_t dict_len, int num_values,
OutType* __restrict__ out, int64_t stride, bool* __restrict__ decode_error);
private:
/// Compute the number of values with the given bit width that can be unpacked from
/// an input buffer of 'in_bytes' into an output buffer with space for 'num_values'.
static int64_t NumValuesToUnpack(int bit_width, int64_t in_bytes, int64_t num_values);
};
}