be/src/util/simd/parquet_kernels.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include "util/simd/parquet_kernels.h" |
19 | | |
20 | | #include <array> |
21 | | #include <bit> |
22 | | #include <cmath> |
23 | | #include <cstring> |
24 | | #include <type_traits> |
25 | | |
26 | | #if defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__)) |
27 | | #include <immintrin.h> |
28 | | #define DORIS_PARQUET_X86_SIMD |
29 | | #endif |
30 | | |
31 | | namespace doris::simd { |
32 | | namespace { |
33 | | |
34 | | #ifdef DORIS_PARQUET_X86_SIMD |
35 | | // Keep x86-only dispatch helpers out of scalar-only builds so warning-clean ARM builds do not |
36 | | // depend on compiler-specific unused-function behavior. |
37 | 12.8k | bool has_avx2() { |
38 | 12.8k | return __builtin_cpu_supports("avx2"); |
39 | 12.8k | } |
40 | | |
41 | | void byte_stream_split_decode_scalar(const uint8_t* src, size_t width, size_t offset, |
42 | 5 | size_t num_values, size_t stride, uint8_t* dest) { |
43 | 13 | for (size_t row = 0; row < num_values; ++row) { |
44 | 56 | for (size_t byte = 0; byte < width; ++byte) { |
45 | 48 | dest[row * width + byte] = src[byte * stride + offset + row]; |
46 | 48 | } |
47 | 8 | } |
48 | 5 | } |
49 | | #endif |
50 | | |
51 | | template <typename T> |
52 | 80 | bool scalar_compare(T lhs, T rhs, RawComparisonOp op) { |
53 | 80 | const auto equal = [](T left, T right) { |
54 | 7 | if constexpr (std::is_floating_point_v<T>) { |
55 | 7 | return (std::isnan(left) && std::isnan(right)) || left == right; |
56 | 7 | } |
57 | 0 | return left == right; |
58 | 7 | }; parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIfEEbT_S3_NS0_15RawComparisonOpEENKUlffE_clEff Line | Count | Source | 53 | 7 | const auto equal = [](T left, T right) { | 54 | 7 | if constexpr (std::is_floating_point_v<T>) { | 55 | 7 | return (std::isnan(left) && std::isnan(right)) || left == right; | 56 | 7 | } | 57 | 0 | return left == right; | 58 | 7 | }; |
Unexecuted instantiation: parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIdEEbT_S3_NS0_15RawComparisonOpEENKUlddE_clEdd Unexecuted instantiation: parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIiEEbT_S3_NS0_15RawComparisonOpEENKUliiE_clEii Unexecuted instantiation: parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIlEEbT_S3_NS0_15RawComparisonOpEENKUlllE_clEll |
59 | 80 | const auto greater = [](T left, T right) { |
60 | 73 | if constexpr (std::is_floating_point_v<T>) { |
61 | | // Match Doris Compare: NaN is equal to NaN and greater than every finite value. |
62 | 10 | if (std::isnan(right)) { |
63 | 3 | return false; |
64 | 3 | } |
65 | 7 | if (std::isnan(left)) { |
66 | 2 | return true; |
67 | 2 | } |
68 | 7 | } |
69 | 5 | return left > right; |
70 | 73 | }; parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIfEEbT_S3_NS0_15RawComparisonOpEENKUlffE0_clEff Line | Count | Source | 59 | 10 | const auto greater = [](T left, T right) { | 60 | 10 | if constexpr (std::is_floating_point_v<T>) { | 61 | | // Match Doris Compare: NaN is equal to NaN and greater than every finite value. | 62 | 10 | if (std::isnan(right)) { | 63 | 3 | return false; | 64 | 3 | } | 65 | 7 | if (std::isnan(left)) { | 66 | 2 | return true; | 67 | 2 | } | 68 | 7 | } | 69 | 5 | return left > right; | 70 | 10 | }; |
Unexecuted instantiation: parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIdEEbT_S3_NS0_15RawComparisonOpEENKUlddE0_clEdd parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIiEEbT_S3_NS0_15RawComparisonOpEENKUliiE0_clEii Line | Count | Source | 59 | 63 | const auto greater = [](T left, T right) { | 60 | | if constexpr (std::is_floating_point_v<T>) { | 61 | | // Match Doris Compare: NaN is equal to NaN and greater than every finite value. | 62 | | if (std::isnan(right)) { | 63 | | return false; | 64 | | } | 65 | | if (std::isnan(left)) { | 66 | | return true; | 67 | | } | 68 | | } | 69 | 63 | return left > right; | 70 | 63 | }; |
Unexecuted instantiation: parquet_kernels.cpp:_ZZN5doris4simd12_GLOBAL__N_114scalar_compareIlEEbT_S3_NS0_15RawComparisonOpEENKUlllE0_clEll |
71 | 80 | switch (op) { |
72 | 7 | case RawComparisonOp::EQ: |
73 | 7 | return equal(lhs, rhs); |
74 | 0 | case RawComparisonOp::NE: |
75 | 0 | return !equal(lhs, rhs); |
76 | 7 | case RawComparisonOp::LT: |
77 | 7 | return greater(rhs, lhs); |
78 | 3 | case RawComparisonOp::LE: |
79 | 3 | return !greater(lhs, rhs); |
80 | 53 | case RawComparisonOp::GT: |
81 | 53 | return greater(lhs, rhs); |
82 | 10 | case RawComparisonOp::GE: |
83 | 10 | return !greater(rhs, lhs); |
84 | 80 | } |
85 | 0 | __builtin_unreachable(); |
86 | 80 | } parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_114scalar_compareIfEEbT_S3_NS0_15RawComparisonOpE Line | Count | Source | 52 | 17 | bool scalar_compare(T lhs, T rhs, RawComparisonOp op) { | 53 | 17 | const auto equal = [](T left, T right) { | 54 | 17 | if constexpr (std::is_floating_point_v<T>) { | 55 | 17 | return (std::isnan(left) && std::isnan(right)) || left == right; | 56 | 17 | } | 57 | 17 | return left == right; | 58 | 17 | }; | 59 | 17 | const auto greater = [](T left, T right) { | 60 | 17 | if constexpr (std::is_floating_point_v<T>) { | 61 | | // Match Doris Compare: NaN is equal to NaN and greater than every finite value. | 62 | 17 | if (std::isnan(right)) { | 63 | 17 | return false; | 64 | 17 | } | 65 | 17 | if (std::isnan(left)) { | 66 | 17 | return true; | 67 | 17 | } | 68 | 17 | } | 69 | 17 | return left > right; | 70 | 17 | }; | 71 | 17 | switch (op) { | 72 | 7 | case RawComparisonOp::EQ: | 73 | 7 | return equal(lhs, rhs); | 74 | 0 | case RawComparisonOp::NE: | 75 | 0 | return !equal(lhs, rhs); | 76 | 0 | case RawComparisonOp::LT: | 77 | 0 | return greater(rhs, lhs); | 78 | 3 | case RawComparisonOp::LE: | 79 | 3 | return !greater(lhs, rhs); | 80 | 7 | case RawComparisonOp::GT: | 81 | 7 | return greater(lhs, rhs); | 82 | 0 | case RawComparisonOp::GE: | 83 | 0 | return !greater(rhs, lhs); | 84 | 17 | } | 85 | 0 | __builtin_unreachable(); | 86 | 17 | } |
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_114scalar_compareIdEEbT_S3_NS0_15RawComparisonOpE parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_114scalar_compareIiEEbT_S3_NS0_15RawComparisonOpE Line | Count | Source | 52 | 63 | bool scalar_compare(T lhs, T rhs, RawComparisonOp op) { | 53 | 63 | const auto equal = [](T left, T right) { | 54 | 63 | if constexpr (std::is_floating_point_v<T>) { | 55 | 63 | return (std::isnan(left) && std::isnan(right)) || left == right; | 56 | 63 | } | 57 | 63 | return left == right; | 58 | 63 | }; | 59 | 63 | const auto greater = [](T left, T right) { | 60 | 63 | if constexpr (std::is_floating_point_v<T>) { | 61 | | // Match Doris Compare: NaN is equal to NaN and greater than every finite value. | 62 | 63 | if (std::isnan(right)) { | 63 | 63 | return false; | 64 | 63 | } | 65 | 63 | if (std::isnan(left)) { | 66 | 63 | return true; | 67 | 63 | } | 68 | 63 | } | 69 | 63 | return left > right; | 70 | 63 | }; | 71 | 63 | switch (op) { | 72 | 0 | case RawComparisonOp::EQ: | 73 | 0 | return equal(lhs, rhs); | 74 | 0 | case RawComparisonOp::NE: | 75 | 0 | return !equal(lhs, rhs); | 76 | 7 | case RawComparisonOp::LT: | 77 | 7 | return greater(rhs, lhs); | 78 | 0 | case RawComparisonOp::LE: | 79 | 0 | return !greater(lhs, rhs); | 80 | 46 | case RawComparisonOp::GT: | 81 | 46 | return greater(lhs, rhs); | 82 | 10 | case RawComparisonOp::GE: | 83 | 10 | return !greater(rhs, lhs); | 84 | 63 | } | 85 | 0 | __builtin_unreachable(); | 86 | 63 | } |
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_114scalar_compareIlEEbT_S3_NS0_15RawComparisonOpE |
87 | | |
88 | | #ifdef DORIS_PARQUET_X86_SIMD |
89 | | template <size_t WIDTH> |
90 | | __attribute__((target("avx2"))) void byte_stream_split_decode_avx2(const uint8_t* src, |
91 | | size_t offset, size_t num_values, |
92 | 5 | size_t stride, uint8_t* dest) { |
93 | 5 | static_assert(WIDTH == 4 || WIDTH == 8); |
94 | 5 | constexpr size_t STEPS = WIDTH == 8 ? 3 : 2; |
95 | 5 | constexpr size_t LANES = sizeof(__m256i); |
96 | 5 | const size_t blocks = num_values / LANES; |
97 | 5 | __m256i stage[STEPS + 1][WIDTH]; |
98 | 5 | __m256i result[WIDTH]; |
99 | | |
100 | 32.7k | for (size_t block = 0; block < blocks; ++block) { |
101 | 163k | for (size_t stream = 0; stream < WIDTH; ++stream) { |
102 | 131k | stage[0][stream] = _mm256_loadu_si256(reinterpret_cast<const __m256i*>( |
103 | 131k | src + stream * stride + offset + block * LANES)); |
104 | 131k | } |
105 | 98.3k | for (size_t step = 0; step < STEPS; ++step) { |
106 | | // AVX2 unpack instructions operate independently in each 128-bit half. Keep the |
107 | | // byte-transpose hierarchy lane-local, then stitch the halves only after the last |
108 | | // unpack so every output vector contains contiguous decoded rows. |
109 | 196k | for (size_t pair = 0; pair < WIDTH / 2; ++pair) { |
110 | 131k | stage[step + 1][pair * 2] = |
111 | 131k | _mm256_unpacklo_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]); |
112 | 131k | stage[step + 1][pair * 2 + 1] = |
113 | 131k | _mm256_unpackhi_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]); |
114 | 131k | } |
115 | 65.5k | } |
116 | 98.3k | for (size_t pair = 0; pair < WIDTH / 2; ++pair) { |
117 | 65.5k | result[pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2], |
118 | 65.5k | stage[STEPS][pair * 2 + 1], 0x20); |
119 | 65.5k | result[WIDTH / 2 + pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2], |
120 | 65.5k | stage[STEPS][pair * 2 + 1], 0x31); |
121 | 65.5k | } |
122 | 163k | for (size_t lane = 0; lane < WIDTH; ++lane) { |
123 | 131k | _mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + (block * WIDTH + lane) * LANES), |
124 | 131k | result[lane]); |
125 | 131k | } |
126 | 32.7k | } |
127 | 5 | const size_t processed = blocks * LANES; |
128 | 5 | byte_stream_split_decode_scalar(src, WIDTH, offset + processed, num_values - processed, stride, |
129 | 5 | dest + processed * WIDTH); |
130 | 5 | } parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_129byte_stream_split_decode_avx2ILm4EEEvPKhmmmPh Line | Count | Source | 92 | 3 | size_t stride, uint8_t* dest) { | 93 | 3 | static_assert(WIDTH == 4 || WIDTH == 8); | 94 | 3 | constexpr size_t STEPS = WIDTH == 8 ? 3 : 2; | 95 | 3 | constexpr size_t LANES = sizeof(__m256i); | 96 | 3 | const size_t blocks = num_values / LANES; | 97 | 3 | __m256i stage[STEPS + 1][WIDTH]; | 98 | 3 | __m256i result[WIDTH]; | 99 | | | 100 | 32.7k | for (size_t block = 0; block < blocks; ++block) { | 101 | 163k | for (size_t stream = 0; stream < WIDTH; ++stream) { | 102 | 131k | stage[0][stream] = _mm256_loadu_si256(reinterpret_cast<const __m256i*>( | 103 | 131k | src + stream * stride + offset + block * LANES)); | 104 | 131k | } | 105 | 98.3k | for (size_t step = 0; step < STEPS; ++step) { | 106 | | // AVX2 unpack instructions operate independently in each 128-bit half. Keep the | 107 | | // byte-transpose hierarchy lane-local, then stitch the halves only after the last | 108 | | // unpack so every output vector contains contiguous decoded rows. | 109 | 196k | for (size_t pair = 0; pair < WIDTH / 2; ++pair) { | 110 | 131k | stage[step + 1][pair * 2] = | 111 | 131k | _mm256_unpacklo_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]); | 112 | 131k | stage[step + 1][pair * 2 + 1] = | 113 | 131k | _mm256_unpackhi_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]); | 114 | 131k | } | 115 | 65.5k | } | 116 | 98.3k | for (size_t pair = 0; pair < WIDTH / 2; ++pair) { | 117 | 65.5k | result[pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2], | 118 | 65.5k | stage[STEPS][pair * 2 + 1], 0x20); | 119 | 65.5k | result[WIDTH / 2 + pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2], | 120 | 65.5k | stage[STEPS][pair * 2 + 1], 0x31); | 121 | 65.5k | } | 122 | 163k | for (size_t lane = 0; lane < WIDTH; ++lane) { | 123 | 131k | _mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + (block * WIDTH + lane) * LANES), | 124 | 131k | result[lane]); | 125 | 131k | } | 126 | 32.7k | } | 127 | 3 | const size_t processed = blocks * LANES; | 128 | 3 | byte_stream_split_decode_scalar(src, WIDTH, offset + processed, num_values - processed, stride, | 129 | 3 | dest + processed * WIDTH); | 130 | 3 | } |
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_129byte_stream_split_decode_avx2ILm8EEEvPKhmmmPh Line | Count | Source | 92 | 2 | size_t stride, uint8_t* dest) { | 93 | 2 | static_assert(WIDTH == 4 || WIDTH == 8); | 94 | 2 | constexpr size_t STEPS = WIDTH == 8 ? 3 : 2; | 95 | 2 | constexpr size_t LANES = sizeof(__m256i); | 96 | 2 | const size_t blocks = num_values / LANES; | 97 | 2 | __m256i stage[STEPS + 1][WIDTH]; | 98 | 2 | __m256i result[WIDTH]; | 99 | | | 100 | 5 | for (size_t block = 0; block < blocks; ++block) { | 101 | 27 | for (size_t stream = 0; stream < WIDTH; ++stream) { | 102 | 24 | stage[0][stream] = _mm256_loadu_si256(reinterpret_cast<const __m256i*>( | 103 | 24 | src + stream * stride + offset + block * LANES)); | 104 | 24 | } | 105 | 12 | for (size_t step = 0; step < STEPS; ++step) { | 106 | | // AVX2 unpack instructions operate independently in each 128-bit half. Keep the | 107 | | // byte-transpose hierarchy lane-local, then stitch the halves only after the last | 108 | | // unpack so every output vector contains contiguous decoded rows. | 109 | 45 | for (size_t pair = 0; pair < WIDTH / 2; ++pair) { | 110 | 36 | stage[step + 1][pair * 2] = | 111 | 36 | _mm256_unpacklo_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]); | 112 | 36 | stage[step + 1][pair * 2 + 1] = | 113 | 36 | _mm256_unpackhi_epi8(stage[step][pair], stage[step][WIDTH / 2 + pair]); | 114 | 36 | } | 115 | 9 | } | 116 | 15 | for (size_t pair = 0; pair < WIDTH / 2; ++pair) { | 117 | 12 | result[pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2], | 118 | 12 | stage[STEPS][pair * 2 + 1], 0x20); | 119 | 12 | result[WIDTH / 2 + pair] = _mm256_permute2x128_si256(stage[STEPS][pair * 2], | 120 | 12 | stage[STEPS][pair * 2 + 1], 0x31); | 121 | 12 | } | 122 | 27 | for (size_t lane = 0; lane < WIDTH; ++lane) { | 123 | 24 | _mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + (block * WIDTH + lane) * LANES), | 124 | 24 | result[lane]); | 125 | 24 | } | 126 | 3 | } | 127 | 2 | const size_t processed = blocks * LANES; | 128 | 2 | byte_stream_split_decode_scalar(src, WIDTH, offset + processed, num_values - processed, stride, | 129 | 2 | dest + processed * WIDTH); | 130 | 2 | } |
|
131 | | |
132 | | __attribute__((target("avx2"))) void delta_decode_int32_avx2(int32_t* values, size_t count, |
133 | | int32_t min_delta, |
134 | 12.5k | int32_t* last_value) { |
135 | 12.5k | const __m256i min_delta_vec = _mm256_set1_epi32(min_delta); |
136 | 12.5k | const size_t vector_count = count / 8 * 8; |
137 | 192k | for (size_t row = 0; row < vector_count; row += 8) { |
138 | 180k | __m256i value = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(values + row)); |
139 | 180k | value = _mm256_add_epi32(value, min_delta_vec); |
140 | 180k | value = _mm256_add_epi32(value, _mm256_slli_si256(value, 4)); |
141 | 180k | value = _mm256_add_epi32(value, _mm256_slli_si256(value, 8)); |
142 | 180k | _mm256_storeu_si256(reinterpret_cast<__m256i*>(values + row), value); |
143 | 180k | } |
144 | 12.5k | __m128i carry = _mm_set1_epi32(*last_value); |
145 | 373k | for (size_t row = 0; row < vector_count; row += 4) { |
146 | 360k | __m128i value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(values + row)); |
147 | 360k | value = _mm_add_epi32(value, carry); |
148 | 360k | _mm_storeu_si128(reinterpret_cast<__m128i*>(values + row), value); |
149 | 360k | carry = _mm_shuffle_epi32(value, _MM_SHUFFLE(3, 3, 3, 3)); |
150 | 360k | } |
151 | 12.5k | if (vector_count != 0) { |
152 | 12.5k | *last_value = _mm_cvtsi128_si32(carry); |
153 | 12.5k | } |
154 | 12.5k | using Unsigned = uint32_t; |
155 | 13.3k | for (size_t row = vector_count; row < count; ++row) { |
156 | 740 | values[row] = static_cast<int32_t>(static_cast<Unsigned>(values[row]) + |
157 | 740 | static_cast<Unsigned>(min_delta) + |
158 | 740 | static_cast<Unsigned>(*last_value)); |
159 | 740 | *last_value = values[row]; |
160 | 740 | } |
161 | 12.5k | } |
162 | | |
163 | | __attribute__((target("avx2"))) void delta_decode_int64_avx2(int64_t* values, size_t count, |
164 | | int64_t min_delta, |
165 | 1 | int64_t* last_value) { |
166 | 1 | const __m256i min_delta_vec = _mm256_set1_epi64x(min_delta); |
167 | 1 | const size_t vector_count = count / 4 * 4; |
168 | 18 | for (size_t row = 0; row < vector_count; row += 4) { |
169 | 17 | __m256i value = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(values + row)); |
170 | 17 | value = _mm256_add_epi64(value, min_delta_vec); |
171 | 17 | value = _mm256_add_epi64(value, _mm256_slli_si256(value, 8)); |
172 | 17 | _mm256_storeu_si256(reinterpret_cast<__m256i*>(values + row), value); |
173 | 17 | } |
174 | 1 | __m128i carry = _mm_set1_epi64x(*last_value); |
175 | 35 | for (size_t row = 0; row < vector_count; row += 2) { |
176 | 34 | __m128i value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(values + row)); |
177 | 34 | value = _mm_add_epi64(value, carry); |
178 | 34 | _mm_storeu_si128(reinterpret_cast<__m128i*>(values + row), value); |
179 | 34 | carry = _mm_unpackhi_epi64(value, value); |
180 | 34 | } |
181 | 1 | if (vector_count != 0) { |
182 | 1 | *last_value = _mm_cvtsi128_si64(carry); |
183 | 1 | } |
184 | 1 | using Unsigned = uint64_t; |
185 | 4 | for (size_t row = vector_count; row < count; ++row) { |
186 | 3 | values[row] = static_cast<int64_t>(static_cast<Unsigned>(values[row]) + |
187 | 3 | static_cast<Unsigned>(min_delta) + |
188 | 3 | static_cast<Unsigned>(*last_value)); |
189 | 3 | *last_value = values[row]; |
190 | 3 | } |
191 | 1 | } |
192 | | |
193 | | __attribute__((target("avx2"))) void dictionary_gather_avx2(const uint8_t* dictionary, |
194 | | const uint32_t* indices, size_t count, |
195 | 19 | size_t value_width, uint8_t* dest) { |
196 | 19 | size_t row = 0; |
197 | 19 | if (value_width == 4) { |
198 | 805 | for (; row + 8 <= count; row += 8) { |
199 | 788 | const __m256i ids = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(indices + row)); |
200 | 788 | const __m256i gathered = |
201 | 788 | _mm256_i32gather_epi32(reinterpret_cast<const int*>(dictionary), ids, 4); |
202 | 788 | _mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + row * 4), gathered); |
203 | 788 | } |
204 | 17 | } else { |
205 | 20 | for (; row + 4 <= count; row += 4) { |
206 | 18 | const __m128i ids = _mm_loadu_si128(reinterpret_cast<const __m128i*>(indices + row)); |
207 | 18 | const __m256i gathered = |
208 | 18 | _mm256_i32gather_epi64(reinterpret_cast<const long long*>(dictionary), ids, 8); |
209 | 18 | _mm256_storeu_si256(reinterpret_cast<__m256i*>(dest + row * 8), gathered); |
210 | 18 | } |
211 | 2 | } |
212 | 36 | for (; row < count; ++row) { |
213 | 17 | memcpy(dest + row * value_width, dictionary + indices[row] * value_width, value_width); |
214 | 17 | } |
215 | 19 | } |
216 | | |
217 | | template <size_t LANES> |
218 | 0 | constexpr auto make_expand_permute_lut() { |
219 | 0 | std::array<std::array<int32_t, 8>, 1U << LANES> lut {}; |
220 | 0 | for (size_t mask = 0; mask < lut.size(); ++mask) { |
221 | 0 | int32_t source = 0; |
222 | 0 | for (size_t lane = 0; lane < LANES; ++lane) { |
223 | 0 | const int32_t value = (mask & (1U << lane)) != 0 ? source++ : 0; |
224 | 0 | if constexpr (LANES == 8) { |
225 | 0 | lut[mask][lane] = value; |
226 | 0 | } else { |
227 | 0 | lut[mask][lane * 2] = value * 2; |
228 | 0 | lut[mask][lane * 2 + 1] = value * 2 + 1; |
229 | 0 | } |
230 | 0 | } |
231 | 0 | } |
232 | 0 | return lut; |
233 | 0 | } Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_123make_expand_permute_lutILm8EEEDav Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_123make_expand_permute_lutILm4EEEDav |
234 | | |
235 | | constexpr auto EXPAND_PERMUTE_32 = make_expand_permute_lut<8>(); |
236 | | constexpr auto EXPAND_PERMUTE_64 = make_expand_permute_lut<4>(); |
237 | | |
238 | | __attribute__((target("avx2"))) void expand_nullable_avx2(uint8_t* bytes, size_t compact_count, |
239 | | const uint8_t* nulls, size_t output_count, |
240 | 7 | size_t value_width) { |
241 | 7 | size_t source = compact_count; |
242 | 7 | size_t output = output_count; |
243 | 7 | if (value_width == 4) { |
244 | 3 | auto* values = reinterpret_cast<int32_t*>(bytes); |
245 | 9 | while (output >= 8) { |
246 | 6 | const size_t start = output - 8; |
247 | 6 | uint32_t valid_mask = 0; |
248 | 54 | for (size_t lane = 0; lane < 8; ++lane) { |
249 | 48 | valid_mask |= static_cast<uint32_t>(nulls[start + lane] == 0) << lane; |
250 | 48 | } |
251 | 6 | const size_t valid = std::popcount(valid_mask); |
252 | 6 | source -= valid; |
253 | 6 | const __m256i load_mask = _mm256_cmpgt_epi32(_mm256_set1_epi32(static_cast<int>(valid)), |
254 | 6 | _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7)); |
255 | 6 | const __m256i compact = _mm256_maskload_epi32(values + source, load_mask); |
256 | 6 | const __m256i permute = _mm256_loadu_si256( |
257 | 6 | reinterpret_cast<const __m256i*>(EXPAND_PERMUTE_32[valid_mask].data())); |
258 | 6 | __m256i expanded = _mm256_permutevar8x32_epi32(compact, permute); |
259 | 6 | const __m256i valid_lanes = _mm256_setr_epi32( |
260 | 6 | -(valid_mask & 1U), -((valid_mask >> 1) & 1U), -((valid_mask >> 2) & 1U), |
261 | 6 | -((valid_mask >> 3) & 1U), -((valid_mask >> 4) & 1U), -((valid_mask >> 5) & 1U), |
262 | 6 | -((valid_mask >> 6) & 1U), -((valid_mask >> 7) & 1U)); |
263 | 6 | expanded = _mm256_and_si256(expanded, valid_lanes); |
264 | 6 | _mm256_storeu_si256(reinterpret_cast<__m256i*>(values + start), expanded); |
265 | 6 | output = start; |
266 | 6 | } |
267 | 4 | } else { |
268 | 4 | auto* values = reinterpret_cast<int64_t*>(bytes); |
269 | 17 | while (output >= 4) { |
270 | 13 | const size_t start = output - 4; |
271 | 13 | uint32_t valid_mask = 0; |
272 | 65 | for (size_t lane = 0; lane < 4; ++lane) { |
273 | 52 | valid_mask |= static_cast<uint32_t>(nulls[start + lane] == 0) << lane; |
274 | 52 | } |
275 | 13 | const size_t valid = std::popcount(valid_mask); |
276 | 13 | source -= valid; |
277 | 13 | const __m256i load_mask = |
278 | 13 | _mm256_setr_epi64x(valid > 0 ? -1LL : 0, valid > 1 ? -1LL : 0, |
279 | 13 | valid > 2 ? -1LL : 0, valid > 3 ? -1LL : 0); |
280 | 13 | const __m256i compact = _mm256_maskload_epi64( |
281 | 13 | reinterpret_cast<const long long*>(values + source), load_mask); |
282 | 13 | const __m256i permute = _mm256_loadu_si256( |
283 | 13 | reinterpret_cast<const __m256i*>(EXPAND_PERMUTE_64[valid_mask].data())); |
284 | 13 | __m256i expanded = _mm256_permutevar8x32_epi32(compact, permute); |
285 | 13 | const __m256i valid_lanes = _mm256_setr_epi64x( |
286 | 13 | (valid_mask & 1U) != 0 ? -1LL : 0, (valid_mask & 2U) != 0 ? -1LL : 0, |
287 | 13 | (valid_mask & 4U) != 0 ? -1LL : 0, (valid_mask & 8U) != 0 ? -1LL : 0); |
288 | 13 | expanded = _mm256_and_si256(expanded, valid_lanes); |
289 | 13 | _mm256_storeu_si256(reinterpret_cast<__m256i*>(values + start), expanded); |
290 | 13 | output = start; |
291 | 13 | } |
292 | 4 | } |
293 | 15 | while (output > 0) { |
294 | 8 | --output; |
295 | 8 | if (nulls[output] != 0) { |
296 | 5 | memset(bytes + output * value_width, 0, value_width); |
297 | 5 | } else { |
298 | 3 | --source; |
299 | 3 | memmove(bytes + output * value_width, bytes + source * value_width, value_width); |
300 | 3 | } |
301 | 8 | } |
302 | 7 | } |
303 | | |
304 | 6 | __attribute__((target("avx2"))) __m256i vector_all(__m256i) { |
305 | 6 | return _mm256_set1_epi32(-1); |
306 | 6 | } |
307 | | |
308 | 3 | __attribute__((target("avx2"))) __m256 vector_all(__m256) { |
309 | 3 | return _mm256_castsi256_ps(_mm256_set1_epi32(-1)); |
310 | 3 | } |
311 | | |
312 | 18 | __attribute__((target("avx2"))) __m256d vector_all(__m256d) { |
313 | 18 | return _mm256_castsi256_pd(_mm256_set1_epi32(-1)); |
314 | 18 | } |
315 | | |
316 | 3 | __attribute__((target("avx2"))) __m256i vector_or(__m256i lhs, __m256i rhs) { |
317 | 3 | return _mm256_or_si256(lhs, rhs); |
318 | 3 | } |
319 | | |
320 | 1 | __attribute__((target("avx2"))) __m256 vector_or(__m256 lhs, __m256 rhs) { |
321 | 1 | return _mm256_or_ps(lhs, rhs); |
322 | 1 | } |
323 | | |
324 | 6 | __attribute__((target("avx2"))) __m256d vector_or(__m256d lhs, __m256d rhs) { |
325 | 6 | return _mm256_or_pd(lhs, rhs); |
326 | 6 | } |
327 | | |
328 | 1 | __attribute__((target("avx2"))) __m256i vector_xor(__m256i lhs, __m256i rhs) { |
329 | 1 | return _mm256_xor_si256(lhs, rhs); |
330 | 1 | } |
331 | | |
332 | 0 | __attribute__((target("avx2"))) __m256 vector_xor(__m256 lhs, __m256 rhs) { |
333 | 0 | return _mm256_xor_ps(lhs, rhs); |
334 | 0 | } |
335 | | |
336 | 2 | __attribute__((target("avx2"))) __m256d vector_xor(__m256d lhs, __m256d rhs) { |
337 | 2 | return _mm256_xor_pd(lhs, rhs); |
338 | 2 | } |
339 | | |
340 | | template <typename Vec> |
341 | | __attribute__((target("avx2"))) Vec combine_comparison(Vec equal, Vec greater, Vec less, |
342 | 27 | RawComparisonOp op) { |
343 | | // Target features do not propagate from AVX2 callers into separately instantiated helpers. |
344 | | // Keep the complete vector operation inside its own target scope for baseline x86 builds. |
345 | 27 | const Vec all = vector_all(equal); |
346 | 27 | switch (op) { |
347 | 5 | case RawComparisonOp::EQ: |
348 | 5 | return equal; |
349 | 3 | case RawComparisonOp::NE: |
350 | 3 | return vector_xor(equal, all); |
351 | 6 | case RawComparisonOp::LT: |
352 | 6 | return less; |
353 | 5 | case RawComparisonOp::LE: |
354 | 5 | return vector_or(less, equal); |
355 | 3 | case RawComparisonOp::GT: |
356 | 3 | return greater; |
357 | 5 | case RawComparisonOp::GE: |
358 | 5 | return vector_or(greater, equal); |
359 | 27 | } |
360 | 0 | __builtin_unreachable(); |
361 | 27 | } parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118combine_comparisonIDv4_xEET_S4_S4_S4_NS0_15RawComparisonOpE Line | Count | Source | 342 | 6 | RawComparisonOp op) { | 343 | | // Target features do not propagate from AVX2 callers into separately instantiated helpers. | 344 | | // Keep the complete vector operation inside its own target scope for baseline x86 builds. | 345 | 6 | const Vec all = vector_all(equal); | 346 | 6 | switch (op) { | 347 | 0 | case RawComparisonOp::EQ: | 348 | 0 | return equal; | 349 | 1 | case RawComparisonOp::NE: | 350 | 1 | return vector_xor(equal, all); | 351 | 2 | case RawComparisonOp::LT: | 352 | 2 | return less; | 353 | 0 | case RawComparisonOp::LE: | 354 | 0 | return vector_or(less, equal); | 355 | 0 | case RawComparisonOp::GT: | 356 | 0 | return greater; | 357 | 3 | case RawComparisonOp::GE: | 358 | 3 | return vector_or(greater, equal); | 359 | 6 | } | 360 | 0 | __builtin_unreachable(); | 361 | 6 | } |
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118combine_comparisonIDv8_fEET_S4_S4_S4_NS0_15RawComparisonOpE Line | Count | Source | 342 | 3 | RawComparisonOp op) { | 343 | | // Target features do not propagate from AVX2 callers into separately instantiated helpers. | 344 | | // Keep the complete vector operation inside its own target scope for baseline x86 builds. | 345 | 3 | const Vec all = vector_all(equal); | 346 | 3 | switch (op) { | 347 | 1 | case RawComparisonOp::EQ: | 348 | 1 | return equal; | 349 | 0 | case RawComparisonOp::NE: | 350 | 0 | return vector_xor(equal, all); | 351 | 0 | case RawComparisonOp::LT: | 352 | 0 | return less; | 353 | 1 | case RawComparisonOp::LE: | 354 | 1 | return vector_or(less, equal); | 355 | 1 | case RawComparisonOp::GT: | 356 | 1 | return greater; | 357 | 0 | case RawComparisonOp::GE: | 358 | 0 | return vector_or(greater, equal); | 359 | 3 | } | 360 | 0 | __builtin_unreachable(); | 361 | 3 | } |
parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118combine_comparisonIDv4_dEET_S4_S4_S4_NS0_15RawComparisonOpE Line | Count | Source | 342 | 18 | RawComparisonOp op) { | 343 | | // Target features do not propagate from AVX2 callers into separately instantiated helpers. | 344 | | // Keep the complete vector operation inside its own target scope for baseline x86 builds. | 345 | 18 | const Vec all = vector_all(equal); | 346 | 18 | switch (op) { | 347 | 4 | case RawComparisonOp::EQ: | 348 | 4 | return equal; | 349 | 2 | case RawComparisonOp::NE: | 350 | 2 | return vector_xor(equal, all); | 351 | 4 | case RawComparisonOp::LT: | 352 | 4 | return less; | 353 | 4 | case RawComparisonOp::LE: | 354 | 4 | return vector_or(less, equal); | 355 | 2 | case RawComparisonOp::GT: | 356 | 2 | return greater; | 357 | 2 | case RawComparisonOp::GE: | 358 | 2 | return vector_or(greater, equal); | 359 | 18 | } | 360 | 0 | __builtin_unreachable(); | 361 | 18 | } |
|
362 | | |
363 | | __attribute__((target("avx2"))) void raw_compare_int32_avx2(const uint8_t* bytes, size_t count, |
364 | | int32_t literal, RawComparisonOp op, |
365 | 2 | uint8_t* matches) { |
366 | 2 | size_t row = 0; |
367 | 2 | const __m256i rhs = _mm256_set1_epi32(literal); |
368 | 4 | for (; row + 8 <= count; row += 8) { |
369 | 2 | const __m256i lhs = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(bytes + row * 4)); |
370 | 2 | const __m256i equal = _mm256_cmpeq_epi32(lhs, rhs); |
371 | 2 | const __m256i greater = _mm256_cmpgt_epi32(lhs, rhs); |
372 | 2 | const __m256i less = _mm256_cmpgt_epi32(rhs, lhs); |
373 | 2 | const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_ps( |
374 | 2 | _mm256_castsi256_ps(combine_comparison(equal, greater, less, op)))); |
375 | 18 | for (size_t lane = 0; lane < 8; ++lane) { |
376 | 16 | matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U); |
377 | 16 | } |
378 | 2 | } |
379 | 8 | for (; row < count; ++row) { |
380 | 6 | int32_t value; |
381 | 6 | memcpy(&value, bytes + row * 4, sizeof(value)); |
382 | 6 | bool keep = false; |
383 | 6 | switch (op) { |
384 | 0 | case RawComparisonOp::EQ: |
385 | 0 | keep = value == literal; |
386 | 0 | break; |
387 | 3 | case RawComparisonOp::NE: |
388 | 3 | keep = value != literal; |
389 | 3 | break; |
390 | 0 | case RawComparisonOp::LT: |
391 | 0 | keep = value < literal; |
392 | 0 | break; |
393 | 0 | case RawComparisonOp::LE: |
394 | 0 | keep = value <= literal; |
395 | 0 | break; |
396 | 0 | case RawComparisonOp::GT: |
397 | 0 | keep = value > literal; |
398 | 0 | break; |
399 | 3 | case RawComparisonOp::GE: |
400 | 3 | keep = value >= literal; |
401 | 3 | break; |
402 | 6 | } |
403 | 6 | matches[row] &= static_cast<uint8_t>(keep); |
404 | 6 | } |
405 | 2 | } |
406 | | |
407 | | __attribute__((target("avx2"))) void raw_compare_int64_avx2(const uint8_t* bytes, size_t count, |
408 | | int64_t literal, RawComparisonOp op, |
409 | 2 | uint8_t* matches) { |
410 | 2 | size_t row = 0; |
411 | 2 | const __m256i rhs = _mm256_set1_epi64x(literal); |
412 | 6 | for (; row + 4 <= count; row += 4) { |
413 | 4 | const __m256i lhs = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(bytes + row * 8)); |
414 | 4 | const __m256i equal = _mm256_cmpeq_epi64(lhs, rhs); |
415 | 4 | const __m256i greater = _mm256_cmpgt_epi64(lhs, rhs); |
416 | 4 | const __m256i less = _mm256_cmpgt_epi64(rhs, lhs); |
417 | 4 | const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_pd( |
418 | 4 | _mm256_castsi256_pd(combine_comparison(equal, greater, less, op)))); |
419 | 20 | for (size_t lane = 0; lane < 4; ++lane) { |
420 | 16 | matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U); |
421 | 16 | } |
422 | 4 | } |
423 | 4 | for (; row < count; ++row) { |
424 | 2 | int64_t value; |
425 | 2 | memcpy(&value, bytes + row * 8, sizeof(value)); |
426 | 2 | bool keep = false; |
427 | 2 | switch (op) { |
428 | 0 | case RawComparisonOp::EQ: |
429 | 0 | keep = value == literal; |
430 | 0 | break; |
431 | 0 | case RawComparisonOp::NE: |
432 | 0 | keep = value != literal; |
433 | 0 | break; |
434 | 1 | case RawComparisonOp::LT: |
435 | 1 | keep = value < literal; |
436 | 1 | break; |
437 | 0 | case RawComparisonOp::LE: |
438 | 0 | keep = value <= literal; |
439 | 0 | break; |
440 | 0 | case RawComparisonOp::GT: |
441 | 0 | keep = value > literal; |
442 | 0 | break; |
443 | 1 | case RawComparisonOp::GE: |
444 | 1 | keep = value >= literal; |
445 | 1 | break; |
446 | 2 | } |
447 | 2 | matches[row] &= static_cast<uint8_t>(keep); |
448 | 2 | } |
449 | 2 | } |
450 | | |
451 | | __attribute__((target("avx2"))) void raw_compare_float_avx2(const uint8_t* bytes, size_t count, |
452 | | float literal, RawComparisonOp op, |
453 | 3 | uint8_t* matches) { |
454 | 3 | size_t row = 0; |
455 | 3 | const __m256 rhs = _mm256_set1_ps(literal); |
456 | 6 | for (; row + 8 <= count; row += 8) { |
457 | 3 | const __m256 lhs = _mm256_loadu_ps(reinterpret_cast<const float*>(bytes + row * 4)); |
458 | 3 | const __m256 lhs_nan = _mm256_cmp_ps(lhs, lhs, _CMP_UNORD_Q); |
459 | 3 | const __m256 rhs_nan = _mm256_cmp_ps(rhs, rhs, _CMP_UNORD_Q); |
460 | 3 | const __m256 both_nan = _mm256_and_ps(lhs_nan, rhs_nan); |
461 | 3 | const __m256 equal = _mm256_or_ps(_mm256_cmp_ps(lhs, rhs, _CMP_EQ_OQ), both_nan); |
462 | 3 | const __m256 greater = _mm256_or_ps(_mm256_cmp_ps(lhs, rhs, _CMP_GT_OQ), |
463 | 3 | _mm256_andnot_ps(rhs_nan, lhs_nan)); |
464 | 3 | const __m256 less = _mm256_or_ps(_mm256_cmp_ps(lhs, rhs, _CMP_LT_OQ), |
465 | 3 | _mm256_andnot_ps(lhs_nan, rhs_nan)); |
466 | 3 | const uint32_t mask = static_cast<uint32_t>( |
467 | 3 | _mm256_movemask_ps(combine_comparison(equal, greater, less, op))); |
468 | 27 | for (size_t lane = 0; lane < 8; ++lane) { |
469 | 24 | matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U); |
470 | 24 | } |
471 | 3 | } |
472 | 12 | for (; row < count; ++row) { |
473 | 9 | float value; |
474 | 9 | memcpy(&value, bytes + row * 4, sizeof(value)); |
475 | 9 | matches[row] &= static_cast<uint8_t>(scalar_compare(value, literal, op)); |
476 | 9 | } |
477 | 3 | } |
478 | | |
479 | | __attribute__((target("avx2"))) void raw_compare_double_avx2(const uint8_t* bytes, size_t count, |
480 | | double literal, RawComparisonOp op, |
481 | 9 | uint8_t* matches) { |
482 | 9 | size_t row = 0; |
483 | 9 | const __m256d rhs = _mm256_set1_pd(literal); |
484 | 27 | for (; row + 4 <= count; row += 4) { |
485 | 18 | const __m256d lhs = _mm256_loadu_pd(reinterpret_cast<const double*>(bytes + row * 8)); |
486 | 18 | const __m256d lhs_nan = _mm256_cmp_pd(lhs, lhs, _CMP_UNORD_Q); |
487 | 18 | const __m256d rhs_nan = _mm256_cmp_pd(rhs, rhs, _CMP_UNORD_Q); |
488 | 18 | const __m256d both_nan = _mm256_and_pd(lhs_nan, rhs_nan); |
489 | 18 | const __m256d equal = _mm256_or_pd(_mm256_cmp_pd(lhs, rhs, _CMP_EQ_OQ), both_nan); |
490 | 18 | const __m256d greater = _mm256_or_pd(_mm256_cmp_pd(lhs, rhs, _CMP_GT_OQ), |
491 | 18 | _mm256_andnot_pd(rhs_nan, lhs_nan)); |
492 | 18 | const __m256d less = _mm256_or_pd(_mm256_cmp_pd(lhs, rhs, _CMP_LT_OQ), |
493 | 18 | _mm256_andnot_pd(lhs_nan, rhs_nan)); |
494 | 18 | const uint32_t mask = static_cast<uint32_t>( |
495 | 18 | _mm256_movemask_pd(combine_comparison(equal, greater, less, op))); |
496 | 90 | for (size_t lane = 0; lane < 4; ++lane) { |
497 | 72 | matches[row + lane] &= static_cast<uint8_t>((mask >> lane) & 1U); |
498 | 72 | } |
499 | 18 | } |
500 | 9 | for (; row < count; ++row) { |
501 | 0 | double value; |
502 | 0 | memcpy(&value, bytes + row * 8, sizeof(value)); |
503 | 0 | matches[row] &= static_cast<uint8_t>(scalar_compare(value, literal, op)); |
504 | 0 | } |
505 | 9 | } |
506 | | #endif |
507 | | |
508 | | template <typename T> |
509 | 132 | void delta_decode_scalar(T* values, size_t count, T min_delta, T* last_value) { |
510 | 132 | using Unsigned = std::make_unsigned_t<T>; |
511 | 305 | for (size_t row = 0; row < count; ++row) { |
512 | 173 | values[row] = static_cast<T>(static_cast<Unsigned>(values[row]) + |
513 | 173 | static_cast<Unsigned>(min_delta) + |
514 | 173 | static_cast<Unsigned>(*last_value)); |
515 | 173 | *last_value = values[row]; |
516 | 173 | } |
517 | 132 | } parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_119delta_decode_scalarIiEEvPT_mS3_S4_ Line | Count | Source | 509 | 132 | void delta_decode_scalar(T* values, size_t count, T min_delta, T* last_value) { | 510 | 132 | using Unsigned = std::make_unsigned_t<T>; | 511 | 305 | for (size_t row = 0; row < count; ++row) { | 512 | 173 | values[row] = static_cast<T>(static_cast<Unsigned>(values[row]) + | 513 | 173 | static_cast<Unsigned>(min_delta) + | 514 | 173 | static_cast<Unsigned>(*last_value)); | 515 | 173 | *last_value = values[row]; | 516 | 173 | } | 517 | 132 | } |
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_119delta_decode_scalarIlEEvPT_mS3_S4_ |
518 | | |
519 | | template <typename T> |
520 | | void raw_compare_scalar(const uint8_t* bytes, size_t count, T literal, RawComparisonOp op, |
521 | 43 | uint8_t* matches) { |
522 | 117 | for (size_t row = 0; row < count; ++row) { |
523 | 74 | if (matches[row] == 0) { |
524 | 3 | continue; |
525 | 3 | } |
526 | 71 | T value; |
527 | 71 | memcpy(&value, bytes + row * sizeof(T), sizeof(T)); |
528 | 71 | matches[row] = static_cast<uint8_t>(scalar_compare(value, literal, op)); |
529 | 71 | } |
530 | 43 | } parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118raw_compare_scalarIiEEvPKhmT_NS0_15RawComparisonOpEPh Line | Count | Source | 521 | 41 | uint8_t* matches) { | 522 | 107 | for (size_t row = 0; row < count; ++row) { | 523 | 66 | if (matches[row] == 0) { | 524 | 3 | continue; | 525 | 3 | } | 526 | 63 | T value; | 527 | 63 | memcpy(&value, bytes + row * sizeof(T), sizeof(T)); | 528 | 63 | matches[row] = static_cast<uint8_t>(scalar_compare(value, literal, op)); | 529 | 63 | } | 530 | 41 | } |
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118raw_compare_scalarIlEEvPKhmT_NS0_15RawComparisonOpEPh parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118raw_compare_scalarIfEEvPKhmT_NS0_15RawComparisonOpEPh Line | Count | Source | 521 | 2 | uint8_t* matches) { | 522 | 10 | for (size_t row = 0; row < count; ++row) { | 523 | 8 | if (matches[row] == 0) { | 524 | 0 | continue; | 525 | 0 | } | 526 | 8 | T value; | 527 | 8 | memcpy(&value, bytes + row * sizeof(T), sizeof(T)); | 528 | 8 | matches[row] = static_cast<uint8_t>(scalar_compare(value, literal, op)); | 529 | 8 | } | 530 | 2 | } |
Unexecuted instantiation: parquet_kernels.cpp:_ZN5doris4simd12_GLOBAL__N_118raw_compare_scalarIdEEvPKhmT_NS0_15RawComparisonOpEPh |
531 | | |
532 | | } // namespace |
533 | | |
534 | | bool try_byte_stream_split_decode(const uint8_t* src, size_t width, size_t offset, |
535 | 21 | size_t num_values, size_t stride, uint8_t* dest) { |
536 | 21 | #ifdef DORIS_PARQUET_X86_SIMD |
537 | 21 | if (has_avx2() && num_values >= 32 && (width == 4 || width == 8)) { |
538 | 5 | if (width == 4) { |
539 | 3 | byte_stream_split_decode_avx2<4>(src, offset, num_values, stride, dest); |
540 | 3 | } else { |
541 | 2 | byte_stream_split_decode_avx2<8>(src, offset, num_values, stride, dest); |
542 | 2 | } |
543 | 5 | return true; |
544 | 5 | } |
545 | 16 | #endif |
546 | 16 | return false; |
547 | 21 | } |
548 | | |
549 | 12.7k | void delta_decode(int32_t* values, size_t count, int32_t min_delta, int32_t* last_value) { |
550 | 12.7k | #ifdef DORIS_PARQUET_X86_SIMD |
551 | 12.7k | if (has_avx2() && count >= 8) { |
552 | 12.5k | delta_decode_int32_avx2(values, count, min_delta, last_value); |
553 | 12.5k | return; |
554 | 12.5k | } |
555 | 132 | #endif |
556 | 132 | delta_decode_scalar(values, count, min_delta, last_value); |
557 | 132 | } |
558 | | |
559 | 1 | void delta_decode(int64_t* values, size_t count, int64_t min_delta, int64_t* last_value) { |
560 | 1 | #ifdef DORIS_PARQUET_X86_SIMD |
561 | 1 | if (has_avx2() && count >= 4) { |
562 | 1 | delta_decode_int64_avx2(values, count, min_delta, last_value); |
563 | 1 | return; |
564 | 1 | } |
565 | 0 | #endif |
566 | 0 | delta_decode_scalar(values, count, min_delta, last_value); |
567 | 0 | } |
568 | | |
569 | | void dictionary_gather(const uint8_t* dictionary, const uint32_t* indices, size_t count, |
570 | 19 | size_t value_width, uint8_t* dest) { |
571 | 19 | #ifdef DORIS_PARQUET_X86_SIMD |
572 | 19 | if (has_avx2() && ((value_width == 4 && count >= 8) || (value_width == 8 && count >= 4))) { |
573 | 19 | dictionary_gather_avx2(dictionary, indices, count, value_width, dest); |
574 | 19 | return; |
575 | 19 | } |
576 | 0 | #endif |
577 | 0 | for (size_t row = 0; row < count; ++row) { |
578 | 0 | memcpy(dest + row * value_width, dictionary + indices[row] * value_width, value_width); |
579 | 0 | } |
580 | 0 | } |
581 | | |
582 | | void expand_nullable_values(uint8_t* values, size_t compact_count, const uint8_t* nulls, |
583 | 11 | size_t output_count, size_t value_width) { |
584 | 11 | #ifdef DORIS_PARQUET_X86_SIMD |
585 | 11 | if (has_avx2() && |
586 | 11 | ((value_width == 4 && output_count >= 8) || (value_width == 8 && output_count >= 4))) { |
587 | | // Backward expansion is required because the compact input and expanded output alias. |
588 | | // Each SIMD block loads all of its source lanes before overwriting the wider destination. |
589 | 7 | expand_nullable_avx2(values, compact_count, nulls, output_count, value_width); |
590 | 7 | return; |
591 | 7 | } |
592 | 4 | #endif |
593 | 4 | size_t source = compact_count; |
594 | 25 | for (size_t output = output_count; output > 0;) { |
595 | 21 | --output; |
596 | 21 | if (nulls[output] != 0) { |
597 | 9 | memset(values + output * value_width, 0, value_width); |
598 | 12 | } else { |
599 | 12 | --source; |
600 | 12 | memmove(values + output * value_width, values + source * value_width, value_width); |
601 | 12 | } |
602 | 21 | } |
603 | 4 | } |
604 | | |
605 | | void raw_compare(const uint8_t* values, size_t count, int32_t literal, RawComparisonOp op, |
606 | 43 | uint8_t* matches) { |
607 | 43 | #ifdef DORIS_PARQUET_X86_SIMD |
608 | 43 | if (has_avx2() && count >= 8) { |
609 | 2 | raw_compare_int32_avx2(values, count, literal, op, matches); |
610 | 2 | return; |
611 | 2 | } |
612 | 41 | #endif |
613 | 41 | raw_compare_scalar(values, count, literal, op, matches); |
614 | 41 | } |
615 | | |
616 | | void raw_compare(const uint8_t* values, size_t count, int64_t literal, RawComparisonOp op, |
617 | 2 | uint8_t* matches) { |
618 | 2 | #ifdef DORIS_PARQUET_X86_SIMD |
619 | 2 | if (has_avx2() && count >= 4) { |
620 | 2 | raw_compare_int64_avx2(values, count, literal, op, matches); |
621 | 2 | return; |
622 | 2 | } |
623 | 0 | #endif |
624 | 0 | raw_compare_scalar(values, count, literal, op, matches); |
625 | 0 | } |
626 | | |
627 | | void raw_compare(const uint8_t* values, size_t count, float literal, RawComparisonOp op, |
628 | 5 | uint8_t* matches) { |
629 | 5 | #ifdef DORIS_PARQUET_X86_SIMD |
630 | 5 | if (has_avx2() && count >= 8) { |
631 | | // Doris orders NaN above every finite value and considers NaN equal to NaN. The SIMD |
632 | | // masks deliberately reconstruct that total order instead of using ordered FP compares. |
633 | 3 | raw_compare_float_avx2(values, count, literal, op, matches); |
634 | 3 | return; |
635 | 3 | } |
636 | 2 | #endif |
637 | 2 | raw_compare_scalar(values, count, literal, op, matches); |
638 | 2 | } |
639 | | |
640 | | void raw_compare(const uint8_t* values, size_t count, double literal, RawComparisonOp op, |
641 | 9 | uint8_t* matches) { |
642 | 9 | #ifdef DORIS_PARQUET_X86_SIMD |
643 | 9 | if (has_avx2() && count >= 4) { |
644 | 9 | raw_compare_double_avx2(values, count, literal, op, matches); |
645 | 9 | return; |
646 | 9 | } |
647 | 0 | #endif |
648 | 0 | raw_compare_scalar(values, count, literal, op, matches); |
649 | 0 | } |
650 | | |
651 | | } // namespace doris::simd |