blob: 9514ac445b09c3dc61e8e69174b38cd28b7128b6 [file]
// 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.
#include "util/simd/parquet_kernels.h"
#include <array>
#include <bit>
#include <cmath>
#include <cstring>
#include <type_traits>
#if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
#include <immintrin.h>
#define DORIS_PARQUET_X86_SIMD
#endif
namespace doris::simd {
namespace {
#ifdef DORIS_PARQUET_X86_SIMD
// Keep x86-only dispatch helpers out of scalar-only builds so warning-clean ARM builds do not
// depend on compiler-specific unused-function behavior.
bool has_avx2() {
return __builtin_cpu_supports("avx2");
}
void byte_stream_split_decode_scalar(const uint8_t* src, size_t width, size_t offset,
size_t num_values, size_t stride, uint8_t* dest) {
for (size_t row = 0; row < num_values; ++row) {
for (size_t byte = 0; byte < width; ++byte) {
dest[row * width + byte] = src[byte * stride + offset + row];
}
}
}
#endif
template <typename T>
bool scalar_compare(T lhs, T rhs, RawComparisonOp op) {
const auto equal = [](T left, T right) {
if constexpr (std::is_floating_point_v<T>) {
return (std::isnan(left) && std::isnan(right)) || left == right;
}
return left == right;
};
const auto greater = [](T left, T right) {
if constexpr (std::is_floating_point_v<T>) {
// Match Doris Compare: NaN is equal to NaN and greater than every finite value.
if (std::isnan(right)) {
return false;
}
if (std::isnan(left)) {
return true;
}
}
return left > right;
};
switch (op) {
case RawComparisonOp::EQ:
return equal(lhs, rhs);
case RawComparisonOp::NE:
return !equal(lhs, rhs);
case RawComparisonOp::LT:
return greater(rhs, lhs);
case RawComparisonOp::LE:
return !greater(lhs, rhs);
case RawComparisonOp::GT:
return greater(lhs, rhs);
case RawComparisonOp::GE:
return !greater(rhs, lhs);
}
__builtin_unreachable();
}
#ifdef DORIS_PARQUET_X86_SIMD
template <size_t WIDTH>
__attribute__((target("avx2"))) void byte_stream_split_decode_avx2(const uint8_t* src,
size_t offset, size_t num_values,
size_t stride, uint8_t* dest) {
static_assert(WIDTH == 4 || WIDTH == 8);
constexpr size_t STEPS = WIDTH == 8 ? 3 : 2;
constexpr size_t LANES = sizeof(__m256i);
const size_t blocks = num_values / LANES;
__m256i stage[STEPS + 1][WIDTH];
__m256i result[WIDTH];
for (size_t block = 0; block < blocks; ++block) {
for (size_t stream = 0; stream < WIDTH; ++stream) {
stage[0][stream] = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(
src + stream * stride + offset + block * LANES));
}
for (size_t step = 0; step < STEPS; ++step) {
// AVX2 unpack instructions operate independently in each 128-bit half. Keep the
// byte-transpose hierarchy lane-local, then stitch the halves only after the last
// unpack so every output vector contains contiguous decoded rows.
for (size_t pair = 0; pair < WIDTH / 2; ++pair) {
stage[step + 1][pair * 2] =
_mm256_unpacklo_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]);
stage[step + 1][pair * 2 + 1] =
_mm256_unpackhi_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]);
}
}
for (size_t pair = 0; pair < WIDTH / 2; ++pair) {
result[pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2],
stage[STEPS][pair * 2 + 1], 0x20);
result[WIDTH / 2 + pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2],
stage[STEPS][pair * 2 + 1], 0x31);
}
for (size_t lane = 0; lane < WIDTH; ++lane) {
_mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + (block * WIDTH + lane) * LANES),
result[lane]);
}
}
const size_t processed = blocks * LANES;
byte_stream_split_decode_scalar(src, WIDTH, offset + processed, num_values - processed, stride,
dest + processed * WIDTH);
}
__attribute__((target("avx2"))) void delta_decode_int32_avx2(int32_t* values, size_t count,
int32_t min_delta,
int32_t* last_value) {
const __m256i min_delta_vec = _mm256_set1_epi32(min_delta);
const size_t vector_count = count / 8 * 8;
for (size_t row = 0; row < vector_count; row += 8) {
__m256i value = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(values + row));
value = _mm256_add_epi32(value, min_delta_vec);
value = _mm256_add_epi32(value, _mm256_slli_si256(value, 4));
value = _mm256_add_epi32(value, _mm256_slli_si256(value, 8));
_mm256_storeu_si256(reinterpret_cast<__m256i*>(values + row), value);
}
__m128i carry = _mm_set1_epi32(*last_value);
for (size_t row = 0; row < vector_count; row += 4) {
__m128i value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(values + row));
value = _mm_add_epi32(value, carry);
_mm_storeu_si128(reinterpret_cast<__m128i*>(values + row), value);
carry = _mm_shuffle_epi32(value, _MM_SHUFFLE(3, 3, 3, 3));
}
if (vector_count != 0) {
*last_value = _mm_cvtsi128_si32(carry);
}
using Unsigned = uint32_t;
for (size_t row = vector_count; row < count; ++row) {
values[row] = static_cast<int32_t>(static_cast<Unsigned>(values[row]) +
static_cast<Unsigned>(min_delta) +
static_cast<Unsigned>(*last_value));
*last_value = values[row];
}
}
__attribute__((target("avx2"))) void delta_decode_int64_avx2(int64_t* values, size_t count,
int64_t min_delta,
int64_t* last_value) {
const __m256i min_delta_vec = _mm256_set1_epi64x(min_delta);
const size_t vector_count = count / 4 * 4;
for (size_t row = 0; row < vector_count; row += 4) {
__m256i value = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(values + row));
value = _mm256_add_epi64(value, min_delta_vec);
value = _mm256_add_epi64(value, _mm256_slli_si256(value, 8));
_mm256_storeu_si256(reinterpret_cast<__m256i*>(values + row), value);
}
__m128i carry = _mm_set1_epi64x(*last_value);
for (size_t row = 0; row < vector_count; row += 2) {
__m128i value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(values + row));
value = _mm_add_epi64(value, carry);
_mm_storeu_si128(reinterpret_cast<__m128i*>(values + row), value);
carry = _mm_unpackhi_epi64(value, value);
}
if (vector_count != 0) {
*last_value = _mm_cvtsi128_si64(carry);
}
using Unsigned = uint64_t;
for (size_t row = vector_count; row < count; ++row) {
values[row] = static_cast<int64_t>(static_cast<Unsigned>(values[row]) +
static_cast<Unsigned>(min_delta) +
static_cast<Unsigned>(*last_value));
*last_value = values[row];
}
}
__attribute__((target("avx2"))) void dictionary_gather_avx2(const uint8_t* dictionary,
const uint32_t* indices, size_t count,
size_t value_width, uint8_t* dest) {
size_t row = 0;
if (value_width == 4) {
for (; row + 8 <= count; row += 8) {
const __m256i ids = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(indices + row));
const __m256i gathered =
_mm256_i32gather_epi32(reinterpret_cast<const int*>(dictionary), ids, 4);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + row * 4), gathered);
}
} else {
for (; row + 4 <= count; row += 4) {
const __m128i ids = _mm_loadu_si128(reinterpret_cast<const __m128i*>(indices + row));
const __m256i gathered =
_mm256_i32gather_epi64(reinterpret_cast<const long long*>(dictionary), ids, 8);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + row * 8), gathered);
}
}
for (; row < count; ++row) {
memcpy(dest + row * value_width, dictionary + indices[row] * value_width, value_width);
}
}
template <size_t LANES>
constexpr auto make_expand_permute_lut() {
std::array<std::array<int32_t, 8>, 1U << LANES> lut {};
for (size_t mask = 0; mask < lut.size(); ++mask) {
int32_t source = 0;
for (size_t lane = 0; lane < LANES; ++lane) {
const int32_t value = (mask & (1U << lane)) != 0 ? source++ : 0;
if constexpr (LANES == 8) {
lut[mask][lane] = value;
} else {
lut[mask][lane * 2] = value * 2;
lut[mask][lane * 2 + 1] = value * 2 + 1;
}
}
}
return lut;
}
constexpr auto EXPAND_PERMUTE_32 = make_expand_permute_lut<8>();
constexpr auto EXPAND_PERMUTE_64 = make_expand_permute_lut<4>();
__attribute__((target("avx2"))) void expand_nullable_avx2(uint8_t* bytes, size_t compact_count,
const uint8_t* nulls, size_t output_count,
size_t value_width) {
size_t source = compact_count;
size_t output = output_count;
if (value_width == 4) {
auto* values = reinterpret_cast<int32_t*>(bytes);
while (output >= 8) {
const size_t start = output - 8;
uint32_t valid_mask = 0;
for (size_t lane = 0; lane < 8; ++lane) {
valid_mask |= static_cast<uint32_t>(nulls[start + lane] == 0) << lane;
}
const size_t valid = std::popcount(valid_mask);
source -= valid;
const __m256i load_mask = _mm256_cmpgt_epi32(_mm256_set1_epi32(static_cast<int>(valid)),
_mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7));
const __m256i compact = _mm256_maskload_epi32(values + source, load_mask);
const __m256i permute = _mm256_loadu_si256(
reinterpret_cast<const __m256i*>(EXPAND_PERMUTE_32[valid_mask].data()));
__m256i expanded = _mm256_permutevar8x32_epi32(compact, permute);
const __m256i valid_lanes = _mm256_setr_epi32(
-(valid_mask & 1U), -((valid_mask >> 1) & 1U), -((valid_mask >> 2) & 1U),
-((valid_mask >> 3) & 1U), -((valid_mask >> 4) & 1U), -((valid_mask >> 5) & 1U),
-((valid_mask >> 6) & 1U), -((valid_mask >> 7) & 1U));
expanded = _mm256_and_si256(expanded, valid_lanes);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(values + start), expanded);
output = start;
}
} else {
auto* values = reinterpret_cast<int64_t*>(bytes);
while (output >= 4) {
const size_t start = output - 4;
uint32_t valid_mask = 0;
for (size_t lane = 0; lane < 4; ++lane) {
valid_mask |= static_cast<uint32_t>(nulls[start + lane] == 0) << lane;
}
const size_t valid = std::popcount(valid_mask);
source -= valid;
const __m256i load_mask =
_mm256_setr_epi64x(valid > 0 ? -1LL : 0, valid > 1 ? -1LL : 0,
valid > 2 ? -1LL : 0, valid > 3 ? -1LL : 0);
const __m256i compact = _mm256_maskload_epi64(
reinterpret_cast<const long long*>(values + source), load_mask);
const __m256i permute = _mm256_loadu_si256(
reinterpret_cast<const __m256i*>(EXPAND_PERMUTE_64[valid_mask].data()));
__m256i expanded = _mm256_permutevar8x32_epi32(compact, permute);
const __m256i valid_lanes = _mm256_setr_epi64x(
(valid_mask & 1U) != 0 ? -1LL : 0, (valid_mask & 2U) != 0 ? -1LL : 0,
(valid_mask & 4U) != 0 ? -1LL : 0, (valid_mask & 8U) != 0 ? -1LL : 0);
expanded = _mm256_and_si256(expanded, valid_lanes);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(values + start), expanded);
output = start;
}
}
while (output > 0) {
--output;
if (nulls[output] != 0) {
memset(bytes + output * value_width, 0, value_width);
} else {
--source;
memmove(bytes + output * value_width, bytes + source * value_width, value_width);
}
}
}
__attribute__((target("avx2"))) __m256i vector_all(__m256i) {
return _mm256_set1_epi32(-1);
}
__attribute__((target("avx2"))) __m256 vector_all(__m256) {
return _mm256_castsi256_ps(_mm256_set1_epi32(-1));
}
__attribute__((target("avx2"))) __m256d vector_all(__m256d) {
return _mm256_castsi256_pd(_mm256_set1_epi32(-1));
}
__attribute__((target("avx2"))) __m256i vector_or(__m256i lhs, __m256i rhs) {
return _mm256_or_si256(lhs, rhs);
}
__attribute__((target("avx2"))) __m256 vector_or(__m256 lhs, __m256 rhs) {
return _mm256_or_ps(lhs, rhs);
}
__attribute__((target("avx2"))) __m256d vector_or(__m256d lhs, __m256d rhs) {
return _mm256_or_pd(lhs, rhs);
}
__attribute__((target("avx2"))) __m256i vector_xor(__m256i lhs, __m256i rhs) {
return _mm256_xor_si256(lhs, rhs);
}
__attribute__((target("avx2"))) __m256 vector_xor(__m256 lhs, __m256 rhs) {
return _mm256_xor_ps(lhs, rhs);
}
__attribute__((target("avx2"))) __m256d vector_xor(__m256d lhs, __m256d rhs) {
return _mm256_xor_pd(lhs, rhs);
}
template <typename Vec>
__attribute__((target("avx2"))) Vec combine_comparison(Vec equal, Vec greater, Vec less,
RawComparisonOp op) {
// Target features do not propagate from AVX2 callers into separately instantiated helpers.
// Keep the complete vector operation inside its own target scope for baseline x86 builds.
const Vec all = vector_all(equal);
switch (op) {
case RawComparisonOp::EQ:
return equal;
case RawComparisonOp::NE:
return vector_xor(equal, all);
case RawComparisonOp::LT:
return less;
case RawComparisonOp::LE:
return vector_or(less, equal);
case RawComparisonOp::GT:
return greater;
case RawComparisonOp::GE:
return vector_or(greater, equal);
}
__builtin_unreachable();
}
__attribute__((target("avx2"))) void raw_compare_int32_avx2(const uint8_t* bytes, size_t count,
int32_t literal, RawComparisonOp op,
uint8_t* matches) {
size_t row = 0;
const __m256i rhs = _mm256_set1_epi32(literal);
for (; row + 8 <= count; row += 8) {
const __m256i lhs = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(bytes + row * 4));
const __m256i equal = _mm256_cmpeq_epi32(lhs, rhs);
const __m256i greater = _mm256_cmpgt_epi32(lhs, rhs);
const __m256i less = _mm256_cmpgt_epi32(rhs, lhs);
const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_ps(
_mm256_castsi256_ps(combine_comparison(equal, greater, less, op))));
for (size_t lane = 0; lane < 8; ++lane) {
matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U);
}
}
for (; row < count; ++row) {
int32_t value;
memcpy(&value, bytes + row * 4, sizeof(value));
bool keep = false;
switch (op) {
case RawComparisonOp::EQ:
keep = value == literal;
break;
case RawComparisonOp::NE:
keep = value != literal;
break;
case RawComparisonOp::LT:
keep = value < literal;
break;
case RawComparisonOp::LE:
keep = value <= literal;
break;
case RawComparisonOp::GT:
keep = value > literal;
break;
case RawComparisonOp::GE:
keep = value >= literal;
break;
}
matches[row] &= static_cast<uint8_t>(keep);
}
}
__attribute__((target("avx2"))) void raw_compare_int64_avx2(const uint8_t* bytes, size_t count,
int64_t literal, RawComparisonOp op,
uint8_t* matches) {
size_t row = 0;
const __m256i rhs = _mm256_set1_epi64x(literal);
for (; row + 4 <= count; row += 4) {
const __m256i lhs = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(bytes + row * 8));
const __m256i equal = _mm256_cmpeq_epi64(lhs, rhs);
const __m256i greater = _mm256_cmpgt_epi64(lhs, rhs);
const __m256i less = _mm256_cmpgt_epi64(rhs, lhs);
const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_pd(
_mm256_castsi256_pd(combine_comparison(equal, greater, less, op))));
for (size_t lane = 0; lane < 4; ++lane) {
matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U);
}
}
for (; row < count; ++row) {
int64_t value;
memcpy(&value, bytes + row * 8, sizeof(value));
bool keep = false;
switch (op) {
case RawComparisonOp::EQ:
keep = value == literal;
break;
case RawComparisonOp::NE:
keep = value != literal;
break;
case RawComparisonOp::LT:
keep = value < literal;
break;
case RawComparisonOp::LE:
keep = value <= literal;
break;
case RawComparisonOp::GT:
keep = value > literal;
break;
case RawComparisonOp::GE:
keep = value >= literal;
break;
}
matches[row] &= static_cast<uint8_t>(keep);
}
}
__attribute__((target("avx2"))) void raw_compare_float_avx2(const uint8_t* bytes, size_t count,
float literal, RawComparisonOp op,
uint8_t* matches) {
size_t row = 0;
const __m256 rhs = _mm256_set1_ps(literal);
for (; row + 8 <= count; row += 8) {
const __m256 lhs = _mm256_loadu_ps(reinterpret_cast<const float*>(bytes + row * 4));
const __m256 lhs_nan = _mm256_cmp_ps(lhs, lhs, _CMP_UNORD_Q);
const __m256 rhs_nan = _mm256_cmp_ps(rhs, rhs, _CMP_UNORD_Q);
const __m256 both_nan = _mm256_and_ps(lhs_nan, rhs_nan);
const __m256 equal = _mm256_or_ps(_mm256_cmp_ps(lhs, rhs, _CMP_EQ_OQ), both_nan);
const __m256 greater = _mm256_or_ps(_mm256_cmp_ps(lhs, rhs, _CMP_GT_OQ),
_mm256_andnot_ps(rhs_nan, lhs_nan));
const __m256 less = _mm256_or_ps(_mm256_cmp_ps(lhs, rhs, _CMP_LT_OQ),
_mm256_andnot_ps(lhs_nan, rhs_nan));
const uint32_t mask = static_cast<uint32_t>(
_mm256_movemask_ps(combine_comparison(equal, greater, less, op)));
for (size_t lane = 0; lane < 8; ++lane) {
matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U);
}
}
for (; row < count; ++row) {
float value;
memcpy(&value, bytes + row * 4, sizeof(value));
matches[row] &= static_cast<uint8_t>(scalar_compare(value, literal, op));
}
}
__attribute__((target("avx2"))) void raw_compare_double_avx2(const uint8_t* bytes, size_t count,
double literal, RawComparisonOp op,
uint8_t* matches) {
size_t row = 0;
const __m256d rhs = _mm256_set1_pd(literal);
for (; row + 4 <= count; row += 4) {
const __m256d lhs = _mm256_loadu_pd(reinterpret_cast<const double*>(bytes + row * 8));
const __m256d lhs_nan = _mm256_cmp_pd(lhs, lhs, _CMP_UNORD_Q);
const __m256d rhs_nan = _mm256_cmp_pd(rhs, rhs, _CMP_UNORD_Q);
const __m256d both_nan = _mm256_and_pd(lhs_nan, rhs_nan);
const __m256d equal = _mm256_or_pd(_mm256_cmp_pd(lhs, rhs, _CMP_EQ_OQ), both_nan);
const __m256d greater = _mm256_or_pd(_mm256_cmp_pd(lhs, rhs, _CMP_GT_OQ),
_mm256_andnot_pd(rhs_nan, lhs_nan));
const __m256d less = _mm256_or_pd(_mm256_cmp_pd(lhs, rhs, _CMP_LT_OQ),
_mm256_andnot_pd(lhs_nan, rhs_nan));
const uint32_t mask = static_cast<uint32_t>(
_mm256_movemask_pd(combine_comparison(equal, greater, less, op)));
for (size_t lane = 0; lane < 4; ++lane) {
matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U);
}
}
for (; row < count; ++row) {
double value;
memcpy(&value, bytes + row * 8, sizeof(value));
matches[row] &= static_cast<uint8_t>(scalar_compare(value, literal, op));
}
}
#endif
template <typename T>
void delta_decode_scalar(T* values, size_t count, T min_delta, T* last_value) {
using Unsigned = std::make_unsigned_t<T>;
for (size_t row = 0; row < count; ++row) {
values[row] = static_cast<T>(static_cast<Unsigned>(values[row]) +
static_cast<Unsigned>(min_delta) +
static_cast<Unsigned>(*last_value));
*last_value = values[row];
}
}
template <typename T>
void raw_compare_scalar(const uint8_t* bytes, size_t count, T literal, RawComparisonOp op,
uint8_t* matches) {
for (size_t row = 0; row < count; ++row) {
if (matches[row] == 0) {
continue;
}
T value;
memcpy(&value, bytes + row * sizeof(T), sizeof(T));
matches[row] = static_cast<uint8_t>(scalar_compare(value, literal, op));
}
}
} // namespace
bool try_byte_stream_split_decode(const uint8_t* src, size_t width, size_t offset,
size_t num_values, size_t stride, uint8_t* dest) {
#ifdef DORIS_PARQUET_X86_SIMD
if (has_avx2() && num_values >= 32 && (width == 4 || width == 8)) {
if (width == 4) {
byte_stream_split_decode_avx2<4>(src, offset, num_values, stride, dest);
} else {
byte_stream_split_decode_avx2<8>(src, offset, num_values, stride, dest);
}
return true;
}
#endif
return false;
}
void delta_decode(int32_t* values, size_t count, int32_t min_delta, int32_t* last_value) {
#ifdef DORIS_PARQUET_X86_SIMD
if (has_avx2() && count >= 8) {
delta_decode_int32_avx2(values, count, min_delta, last_value);
return;
}
#endif
delta_decode_scalar(values, count, min_delta, last_value);
}
void delta_decode(int64_t* values, size_t count, int64_t min_delta, int64_t* last_value) {
#ifdef DORIS_PARQUET_X86_SIMD
if (has_avx2() && count >= 4) {
delta_decode_int64_avx2(values, count, min_delta, last_value);
return;
}
#endif
delta_decode_scalar(values, count, min_delta, last_value);
}
void dictionary_gather(const uint8_t* dictionary, const uint32_t* indices, size_t count,
size_t value_width, uint8_t* dest) {
#ifdef DORIS_PARQUET_X86_SIMD
if (has_avx2() && ((value_width == 4 && count >= 8) || (value_width == 8 && count >= 4))) {
dictionary_gather_avx2(dictionary, indices, count, value_width, dest);
return;
}
#endif
for (size_t row = 0; row < count; ++row) {
memcpy(dest + row * value_width, dictionary + indices[row] * value_width, value_width);
}
}
void expand_nullable_values(uint8_t* values, size_t compact_count, const uint8_t* nulls,
size_t output_count, size_t value_width) {
#ifdef DORIS_PARQUET_X86_SIMD
if (has_avx2() &&
((value_width == 4 && output_count >= 8) || (value_width == 8 && output_count >= 4))) {
// Backward expansion is required because the compact input and expanded output alias.
// Each SIMD block loads all of its source lanes before overwriting the wider destination.
expand_nullable_avx2(values, compact_count, nulls, output_count, value_width);
return;
}
#endif
size_t source = compact_count;
for (size_t output = output_count; output > 0;) {
--output;
if (nulls[output] != 0) {
memset(values + output * value_width, 0, value_width);
} else {
--source;
memmove(values + output * value_width, values + source * value_width, value_width);
}
}
}
void raw_compare(const uint8_t* values, size_t count, int32_t literal, RawComparisonOp op,
uint8_t* matches) {
#ifdef DORIS_PARQUET_X86_SIMD
if (has_avx2() && count >= 8) {
raw_compare_int32_avx2(values, count, literal, op, matches);
return;
}
#endif
raw_compare_scalar(values, count, literal, op, matches);
}
void raw_compare(const uint8_t* values, size_t count, int64_t literal, RawComparisonOp op,
uint8_t* matches) {
#ifdef DORIS_PARQUET_X86_SIMD
if (has_avx2() && count >= 4) {
raw_compare_int64_avx2(values, count, literal, op, matches);
return;
}
#endif
raw_compare_scalar(values, count, literal, op, matches);
}
void raw_compare(const uint8_t* values, size_t count, float literal, RawComparisonOp op,
uint8_t* matches) {
#ifdef DORIS_PARQUET_X86_SIMD
if (has_avx2() && count >= 8) {
// Doris orders NaN above every finite value and considers NaN equal to NaN. The SIMD
// masks deliberately reconstruct that total order instead of using ordered FP compares.
raw_compare_float_avx2(values, count, literal, op, matches);
return;
}
#endif
raw_compare_scalar(values, count, literal, op, matches);
}
void raw_compare(const uint8_t* values, size_t count, double literal, RawComparisonOp op,
uint8_t* matches) {
#ifdef DORIS_PARQUET_X86_SIMD
if (has_avx2() && count >= 4) {
raw_compare_double_avx2(values, count, literal, op, matches);
return;
}
#endif
raw_compare_scalar(values, count, literal, op, matches);
}
} // namespace doris::simd