| /* |
| * 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. |
| */ |
| |
| #ifndef ENCODING_TS2DIFF_ENCODER_H |
| #define ENCODING_TS2DIFF_ENCODER_H |
| |
| #include <sys/types.h> |
| |
| #include "common/allocator/alloc_base.h" |
| #include "common/allocator/byte_stream.h" |
| #include "encoder.h" |
| |
| #ifdef ENABLE_SIMD |
| #include "simde/x86/avx2.h" |
| #endif |
| |
| namespace storage { |
| |
| template <typename T> |
| struct SIMDOps; |
| |
| template <> |
| struct SIMDOps<int32_t> { |
| #ifdef ENABLE_SIMD |
| static void rebase(int32_t* arr, int32_t min_val, size_t size) { |
| const simde__m128i min_vec = simde_mm_set1_epi32(min_val); |
| size_t i = 0; |
| for (; i + 3 < size; i += 4) { |
| simde__m128i vec = simde_mm_loadu_si128( |
| reinterpret_cast<const simde__m128i*>(arr + i)); |
| vec = simde_mm_sub_epi32(vec, min_vec); |
| simde_mm_storeu_si128(reinterpret_cast<simde__m128i*>(arr + i), |
| vec); |
| } |
| for (; i < size; ++i) { |
| arr[i] -= min_val; |
| } |
| } |
| #else |
| static void rebase(int32_t* arr, int32_t min_val, size_t size) { |
| for (size_t i = 0; i < size; ++i) { |
| arr[i] -= min_val; |
| } |
| } |
| #endif |
| }; |
| |
| template <> |
| struct SIMDOps<int64_t> { |
| #ifdef ENABLE_SIMD |
| static void rebase(int64_t* arr, int64_t min_val, size_t size) { |
| const simde__m256i min_vec = simde_mm256_set1_epi64x(min_val); |
| size_t i = 0; |
| for (; i + 3 < size; i += 4) { |
| simde__m256i vec = simde_mm256_loadu_si256( |
| reinterpret_cast<const simde__m256i*>(arr + i)); |
| vec = simde_mm256_sub_epi64(vec, min_vec); |
| simde_mm256_storeu_si256(reinterpret_cast<simde__m256i*>(arr + i), |
| vec); |
| } |
| for (; i < size; ++i) { |
| arr[i] -= min_val; |
| } |
| } |
| #else |
| static void rebase(int64_t* arr, int64_t min_val, size_t size) { |
| for (size_t i = 0; i < size; ++i) { |
| arr[i] -= min_val; |
| } |
| } |
| #endif |
| }; |
| |
| template <typename T> |
| class TS2DIFFEncoder : public Encoder { |
| public: |
| TS2DIFFEncoder() { init(); } |
| |
| ~TS2DIFFEncoder() { destroy(); } |
| |
| void reset() override { write_index_ = -1; } |
| |
| void init() { |
| block_size_ = 128; |
| // block_size_ = 16; |
| delta_arr_ = (T*)common::mem_alloc(sizeof(T) * block_size_, |
| common::MOD_TS2DIFF_OBJ); |
| write_index_ = -1; |
| bits_left_ = 8; |
| buffer_ = 0; |
| delta_arr_min_ = 0; |
| delta_arr_max_ = 0; |
| first_value_ = 0; |
| previous_value_ = 0; |
| } |
| |
| void destroy() override { |
| if (delta_arr_ != nullptr) { |
| common::mem_free(delta_arr_); |
| delta_arr_ = nullptr; |
| } |
| } |
| |
| void write_bits(int64_t value, int bits, common::ByteStream& out_stream) { |
| while (bits > 0) { |
| int shift = bits - bits_left_; |
| if (shift >= 0) { |
| buffer_ |= |
| (uint8_t)((value >> shift) & ((1 << bits_left_) - 1)); |
| bits -= bits_left_; |
| bits_left_ = 0; |
| } else { |
| shift = bits_left_ - bits; |
| buffer_ |= (uint8_t)(value << shift); |
| bits_left_ -= bits; |
| bits = 0; |
| } |
| flush_byte_if_full(out_stream); |
| } |
| } |
| |
| void flush_remaining(common::ByteStream& out_stream) { |
| // FIXME bits_left_ != 0 does not means something to be flushed. (=8) |
| if (bits_left_ != 0 && bits_left_ != 8) { |
| bits_left_ = 0; |
| flush_byte_if_full(out_stream); |
| } |
| } |
| |
| void flush_byte_if_full(common::ByteStream& out_stream) { |
| if (bits_left_ == 0) { |
| out_stream.write_buf(&buffer_, 1); |
| buffer_ = 0; |
| bits_left_ = 8; |
| } |
| } |
| |
| void rebase_arr(int i) { delta_arr_[i] = delta_arr_[i] - delta_arr_min_; } |
| |
| int cal_bit_width(T n) { |
| int bit_width = 0; |
| while (n > 0) { |
| bit_width++; |
| n >>= 1; |
| } |
| return bit_width; |
| } |
| |
| int do_encode(T value, common::ByteStream& out_stream); |
| int encode(bool value, common::ByteStream& out_stream) override; |
| int encode(int32_t value, common::ByteStream& out_stream) override; |
| int encode(int64_t value, common::ByteStream& out_stream) override; |
| int encode(float value, common::ByteStream& out_stream) override; |
| int encode(double value, common::ByteStream& out_stream) override; |
| int encode(common::String value, common::ByteStream& out_stream) override; |
| |
| int encode_batch(const int32_t* values, uint32_t count, |
| common::ByteStream& out_stream) override; |
| int encode_batch(const int64_t* values, uint32_t count, |
| common::ByteStream& out_stream) override; |
| |
| int flush(common::ByteStream& out_stream) override; |
| |
| int get_max_byte_size() override { |
| // The meaning of 24 is: index(4)+width(4)+minDeltaBase(8)+firstValue(8) |
| return 24 + write_index_ * 8; |
| } |
| |
| public: |
| int block_size_; |
| T* delta_arr_; |
| T first_value_; |
| T previous_value_; |
| T delta_arr_min_; |
| T delta_arr_max_; |
| uint8_t buffer_; |
| int bits_left_; |
| int write_index_; |
| }; |
| |
| template <typename T> |
| int TS2DIFFEncoder<T>::do_encode(T value, common::ByteStream& out_stream) { |
| if (write_index_ == -1) { |
| first_value_ = value; |
| previous_value_ = first_value_; |
| write_index_++; |
| return common::E_OK; |
| } |
| // Calculate the delta between the current value and the previous_value_ |
| T delta = value - previous_value_; |
| previous_value_ = value; |
| if (write_index_ == 0) { |
| delta_arr_max_ = delta; |
| delta_arr_min_ = delta; |
| } |
| if (delta > delta_arr_max_) { |
| delta_arr_max_ = delta; |
| } |
| if (delta < delta_arr_min_) { |
| delta_arr_min_ = delta; |
| } |
| |
| delta_arr_[write_index_] = delta; |
| write_index_++; |
| |
| if (write_index_ >= block_size_) { |
| return flush(out_stream); |
| } |
| return common::E_OK; |
| } |
| |
| template <> |
| inline int TS2DIFFEncoder<int32_t>::flush(common::ByteStream& out_stream) { |
| int ret = common::E_OK; |
| if (write_index_ == -1) { |
| return common::E_OK; |
| } |
| // Subtract the minimum value for each delta_arr_ item |
| SIMDOps<int32_t>::rebase(delta_arr_, delta_arr_min_, write_index_); |
| // Calculate the bit length of each value to writer |
| int bit_width = cal_bit_width(delta_arr_max_ - delta_arr_min_); |
| // writer header |
| common::SerializationUtil::write_ui32(write_index_, out_stream); |
| common::SerializationUtil::write_ui32(bit_width, out_stream); |
| common::SerializationUtil::write_ui32(delta_arr_min_, out_stream); |
| common::SerializationUtil::write_ui32(first_value_, out_stream); |
| // writer data |
| for (int i = 0; i < write_index_; i++) { |
| write_bits(delta_arr_[i], bit_width, out_stream); |
| } |
| flush_remaining(out_stream); |
| reset(); |
| return ret; |
| } |
| |
| template <> |
| inline int TS2DIFFEncoder<int64_t>::flush(common::ByteStream& out_stream) { |
| int ret = common::E_OK; |
| if (write_index_ == -1) { |
| return common::E_OK; |
| } |
| // Subtract the minimum value for each delta_arr_ item |
| SIMDOps<int64_t>::rebase(delta_arr_, delta_arr_min_, write_index_); |
| // Calculate the bit length of each value to writer |
| int bit_width = cal_bit_width(delta_arr_max_ - delta_arr_min_); |
| // writer header |
| common::SerializationUtil::write_i32(write_index_, out_stream); |
| common::SerializationUtil::write_i32(bit_width, out_stream); |
| common::SerializationUtil::write_i64(delta_arr_min_, out_stream); |
| common::SerializationUtil::write_i64(first_value_, out_stream); |
| // writer data |
| for (int i = 0; i < write_index_; i++) { |
| write_bits(delta_arr_[i], bit_width, out_stream); |
| } |
| flush_remaining(out_stream); |
| reset(); // è¯ä¹‰ï¼ŒwriteIndex=-1; |
| return ret; |
| } |
| |
| // ============================================================================ |
| // Batch encode: INT32 |
| // Adjacent-difference removes sequential dependency; SIMD for delta + min/max. |
| // ============================================================================ |
| |
| template <> |
| inline int TS2DIFFEncoder<int32_t>::encode_batch( |
| const int32_t* values, uint32_t count, common::ByteStream& out_stream) { |
| int ret = common::E_OK; |
| uint32_t offset = 0; |
| |
| while (offset < count) { |
| // Start of new block: store first_value |
| if (write_index_ == -1) { |
| first_value_ = values[offset]; |
| previous_value_ = first_value_; |
| write_index_ = 0; |
| offset++; |
| continue; |
| } |
| |
| // How many deltas fit in current block |
| uint32_t space = static_cast<uint32_t>(block_size_) - write_index_; |
| uint32_t batch = std::min(count - offset, space); |
| |
| // ── Adjacent difference: delta[i] = values[i] - values[i-1] ── |
| // First delta uses previous_value_ |
| delta_arr_[write_index_] = values[offset] - previous_value_; |
| |
| uint32_t i = 1; |
| #ifdef ENABLE_SIMD |
| // SIMD: 4 adjacent differences at a time |
| for (; i + 3 < batch; i += 4) { |
| simde__m128i cur = simde_mm_loadu_si128( |
| reinterpret_cast<const simde__m128i*>(values + offset + i)); |
| simde__m128i prv = simde_mm_loadu_si128( |
| reinterpret_cast<const simde__m128i*>(values + offset + i - 1)); |
| simde__m128i diff = simde_mm_sub_epi32(cur, prv); |
| simde_mm_storeu_si128( |
| reinterpret_cast<simde__m128i*>(delta_arr_ + write_index_ + i), |
| diff); |
| } |
| #endif |
| for (; i < batch; i++) { |
| delta_arr_[write_index_ + i] = |
| values[offset + i] - values[offset + i - 1]; |
| } |
| previous_value_ = values[offset + batch - 1]; |
| |
| // ── Min/max of new deltas ── |
| int32_t local_min = delta_arr_[write_index_]; |
| int32_t local_max = delta_arr_[write_index_]; |
| |
| uint32_t j = 1; |
| #ifdef ENABLE_SIMD |
| if (batch >= 5) { |
| simde__m128i vmin = simde_mm_set1_epi32(local_min); |
| simde__m128i vmax = vmin; |
| for (; j + 3 < batch; j += 4) { |
| simde__m128i v = |
| simde_mm_loadu_si128(reinterpret_cast<const simde__m128i*>( |
| delta_arr_ + write_index_ + j)); |
| vmin = simde_mm_min_epi32(vmin, v); |
| vmax = simde_mm_max_epi32(vmax, v); |
| } |
| // Horizontal reduce |
| int32_t tmp[4]; |
| simde_mm_storeu_si128(reinterpret_cast<simde__m128i*>(tmp), vmin); |
| for (int k = 0; k < 4; k++) |
| if (tmp[k] < local_min) local_min = tmp[k]; |
| simde_mm_storeu_si128(reinterpret_cast<simde__m128i*>(tmp), vmax); |
| for (int k = 0; k < 4; k++) |
| if (tmp[k] > local_max) local_max = tmp[k]; |
| } |
| #endif |
| for (; j < batch; j++) { |
| int32_t d = delta_arr_[write_index_ + j]; |
| if (d < local_min) local_min = d; |
| if (d > local_max) local_max = d; |
| } |
| |
| // Merge with block min/max |
| if (write_index_ == 0) { |
| delta_arr_min_ = local_min; |
| delta_arr_max_ = local_max; |
| } else { |
| if (local_min < delta_arr_min_) delta_arr_min_ = local_min; |
| if (local_max > delta_arr_max_) delta_arr_max_ = local_max; |
| } |
| |
| write_index_ += batch; |
| offset += batch; |
| |
| if (write_index_ >= block_size_) { |
| if (RET_FAIL(flush(out_stream))) return ret; |
| } |
| } |
| return ret; |
| } |
| |
| // ============================================================================ |
| // Batch encode: INT64 |
| // ============================================================================ |
| |
| template <> |
| inline int TS2DIFFEncoder<int64_t>::encode_batch( |
| const int64_t* values, uint32_t count, common::ByteStream& out_stream) { |
| int ret = common::E_OK; |
| uint32_t offset = 0; |
| |
| while (offset < count) { |
| if (write_index_ == -1) { |
| first_value_ = values[offset]; |
| previous_value_ = first_value_; |
| write_index_ = 0; |
| offset++; |
| continue; |
| } |
| |
| uint32_t space = static_cast<uint32_t>(block_size_) - write_index_; |
| uint32_t batch = std::min(count - offset, space); |
| |
| // Adjacent difference |
| delta_arr_[write_index_] = values[offset] - previous_value_; |
| |
| uint32_t i = 1; |
| #ifdef ENABLE_SIMD |
| // SIMD: 2 adjacent differences at a time (128-bit, native NEON) |
| for (; i + 1 < batch; i += 2) { |
| simde__m128i cur = simde_mm_loadu_si128( |
| reinterpret_cast<const simde__m128i*>(values + offset + i)); |
| simde__m128i prv = simde_mm_loadu_si128( |
| reinterpret_cast<const simde__m128i*>(values + offset + i - 1)); |
| simde__m128i diff = simde_mm_sub_epi64(cur, prv); |
| simde_mm_storeu_si128( |
| reinterpret_cast<simde__m128i*>(delta_arr_ + write_index_ + i), |
| diff); |
| } |
| #endif |
| for (; i < batch; i++) { |
| delta_arr_[write_index_ + i] = |
| values[offset + i] - values[offset + i - 1]; |
| } |
| previous_value_ = values[offset + batch - 1]; |
| |
| // Min/max (scalar — no efficient 64-bit SIMD min/max before AVX-512) |
| int64_t local_min = delta_arr_[write_index_]; |
| int64_t local_max = delta_arr_[write_index_]; |
| for (uint32_t j = 1; j < batch; j++) { |
| int64_t d = delta_arr_[write_index_ + j]; |
| if (d < local_min) local_min = d; |
| if (d > local_max) local_max = d; |
| } |
| |
| if (write_index_ == 0) { |
| delta_arr_min_ = local_min; |
| delta_arr_max_ = local_max; |
| } else { |
| if (local_min < delta_arr_min_) delta_arr_min_ = local_min; |
| if (local_max > delta_arr_max_) delta_arr_max_ = local_max; |
| } |
| |
| write_index_ += batch; |
| offset += batch; |
| |
| if (write_index_ >= block_size_) { |
| if (RET_FAIL(flush(out_stream))) return ret; |
| } |
| } |
| return ret; |
| } |
| |
| // Default: unsupported types fall back to base class loop |
| template <typename T> |
| int TS2DIFFEncoder<T>::encode_batch(const int32_t* values, uint32_t count, |
| common::ByteStream& out) { |
| return Encoder::encode_batch(values, count, out); |
| } |
| template <typename T> |
| int TS2DIFFEncoder<T>::encode_batch(const int64_t* values, uint32_t count, |
| common::ByteStream& out) { |
| return Encoder::encode_batch(values, count, out); |
| } |
| |
| class FloatTS2DIFFEncoder : public TS2DIFFEncoder<int32_t> { |
| public: |
| int do_encode(float value, common::ByteStream& out_stream) { |
| int32_t value_int = common::float_to_int(value); |
| return TS2DIFFEncoder<int32_t>::do_encode(value_int, out_stream); |
| } |
| int encode(bool value, common::ByteStream& out_stream); |
| int encode(int32_t value, common::ByteStream& out_stream); |
| int encode(int64_t value, common::ByteStream& out_stream); |
| int encode(float value, common::ByteStream& out_stream); |
| int encode(double value, common::ByteStream& out_stream); |
| }; |
| |
| class DoubleTS2DIFFEncoder : public TS2DIFFEncoder<int64_t> { |
| public: |
| int do_encode(double value, common::ByteStream& out_stream) { |
| int64_t value_long = common::double_to_long(value); |
| return TS2DIFFEncoder<int64_t>::do_encode(value_long, out_stream); |
| } |
| int encode(bool value, common::ByteStream& out_stream); |
| int encode(int32_t value, common::ByteStream& out_stream); |
| int encode(int64_t value, common::ByteStream& out_stream); |
| int encode(float value, common::ByteStream& out_stream); |
| int encode(double value, common::ByteStream& out_stream); |
| }; |
| |
| typedef TS2DIFFEncoder<int32_t> IntTS2DIFFEncoder; |
| typedef TS2DIFFEncoder<int64_t> LongTS2DIFFEncoder; |
| |
| // wrap as Encoder |
| template <> |
| FORCE_INLINE int IntTS2DIFFEncoder::encode(bool value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| template <> |
| FORCE_INLINE int IntTS2DIFFEncoder::encode(int32_t value, |
| common::ByteStream& out) { |
| return do_encode(value, out); |
| } |
| template <> |
| FORCE_INLINE int IntTS2DIFFEncoder::encode(int64_t value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| template <> |
| FORCE_INLINE int IntTS2DIFFEncoder::encode(float value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| template <> |
| FORCE_INLINE int IntTS2DIFFEncoder::encode(double value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| template <> |
| FORCE_INLINE int IntTS2DIFFEncoder::encode(common::String value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| |
| template <> |
| FORCE_INLINE int LongTS2DIFFEncoder::encode(bool value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| template <> |
| FORCE_INLINE int LongTS2DIFFEncoder::encode(int32_t value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| template <> |
| FORCE_INLINE int LongTS2DIFFEncoder::encode(int64_t value, |
| common::ByteStream& out) { |
| return do_encode(value, out); |
| } |
| template <> |
| FORCE_INLINE int LongTS2DIFFEncoder::encode(float value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| template <> |
| FORCE_INLINE int LongTS2DIFFEncoder::encode(double value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| template <> |
| FORCE_INLINE int LongTS2DIFFEncoder::encode(common::String value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| FORCE_INLINE int FloatTS2DIFFEncoder::encode(bool value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| FORCE_INLINE int FloatTS2DIFFEncoder::encode(int32_t value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| FORCE_INLINE int FloatTS2DIFFEncoder::encode(int64_t value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| FORCE_INLINE int FloatTS2DIFFEncoder::encode(float value, |
| common::ByteStream& out) { |
| return do_encode(value, out); |
| } |
| FORCE_INLINE int FloatTS2DIFFEncoder::encode(double value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| |
| FORCE_INLINE int DoubleTS2DIFFEncoder::encode(bool value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| FORCE_INLINE int DoubleTS2DIFFEncoder::encode(int32_t value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| FORCE_INLINE int DoubleTS2DIFFEncoder::encode(int64_t value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| FORCE_INLINE int DoubleTS2DIFFEncoder::encode(float value, |
| common::ByteStream& out) { |
| return common::E_TYPE_NOT_MATCH; |
| } |
| FORCE_INLINE int DoubleTS2DIFFEncoder::encode(double value, |
| common::ByteStream& out) { |
| return do_encode(value, out); |
| } |
| |
| } // end namespace storage |
| #endif // ENCODING_TS2DIFF_ENCODER_H |