HIBF 1.0.0-rc.1
Loading...
Searching...
No Matches
counting_vector.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2006-2026, Knut Reinert & Freie Universität Berlin
2// SPDX-FileCopyrightText: 2016-2026, Knut Reinert & MPI für molekulare Genetik
3// SPDX-License-Identifier: BSD-3-Clause
4
10#pragma once
11
12#include <algorithm> // for transform
13#include <bit> // for countr_zero
14#include <climits> // for CHAR_BIT
15#include <concepts> // for integral
16#include <cstddef> // for size_t
17#include <cstdint> // for uint64_t, uint8_t
18#include <functional> // for minus, plus
19#include <type_traits> // for conditional_t
20#include <vector> // for vector
21
22#include <hibf/contrib/aligned_allocator.hpp> // for aligned_allocator
23#include <hibf/misc/bit_vector.hpp> // for bit_vector
24#include <hibf/misc/divide_and_ceil.hpp> // for divide_and_ceil
25#include <hibf/misc/unreachable.hpp> // for assert, unreachable
26#include <hibf/platform.hpp> // for HIBF_HAS_AVX512
27
28#if HIBF_HAS_AVX512
29# include <simde/x86/avx512/add.h> // for simde_mm512_add_epi16, simde_mm512_add_epi32, simde_mm512_add_...
30# include <simde/x86/avx512/load.h> // for simde_mm512_load_si512
31# include <simde/x86/avx512/mov.h> // for simde_mm512_maskz_mov_epi16, simde_mm512_maskz_mov_epi32, simd...
32# include <simde/x86/avx512/set1.h> // for simde_mm512_set1_epi16, simde_mm512_set1_epi32, simde_mm512_se...
33# include <simde/x86/avx512/store.h> // for simde_mm512_store_si512
34# include <simde/x86/avx512/sub.h> // for simde_mm512_sub_epi16, simde_mm512_sub_epi32, simde_mm512_sub_...
35# include <simde/x86/avx512/types.h> // for simde__m512i
36#endif
37
38namespace seqan::hibf
39{
40
41#if HIBF_HAS_AVX512
43// Since the counting_vector can have different value types, we need specific SIMD instructions for each value type.
44template <std::integral integral_t>
45struct simd_mapping
46{};
47
48// CRTP base class for the simd_mapping, containg common functionality.
49template <typename derived_t, std::integral integral_t>
51struct simd_mapping_crtp
52{
53 // Let `B = sizeof(integral_t) * CHAR_BIT`, e.g. 8 for (u)int8_t, and 16 for (u)int16_t.
54 // We can process `512 / B` bits of the bit_vector at once.
55 static inline constexpr size_t bits_per_iterations = 512u / (sizeof(integral_t) * CHAR_BIT);
56 // clang-format off
57 // The type that is need to represent `bits_per_iterations` bits.
58 using bits_type = std::conditional_t<bits_per_iterations == 64, uint64_t,
59 std::conditional_t<bits_per_iterations == 32, uint32_t,
60 std::conditional_t<bits_per_iterations == 16, uint16_t,
62 // clang-format on
63 static_assert(!std::same_as<bits_type, void>, "Unsupported bits_type.");
64
65 // Takes B bits from the bit_vector and expands them to a bits_type.
66 // E.g., B = 8 : [1,0,1,1,0,0,1,0] -> [0...01, 0...00, 0...01, 0...01, 0...00, 0...00, 0...01, 0...00], where
67 // each element is 64 bits wide.
68 static inline constexpr auto expand_bits(bits_type const * const bits)
69 {
70 return derived_t::mm512_maskz_mov_epi(*bits, derived_t::mm512_set1_epi(1));
71 }
72};
73
74// SIMD instructions for int8_t and uint8_t.
75template <std::integral integral_t>
76 requires (sizeof(integral_t) == 1)
77struct simd_mapping<integral_t> : simd_mapping_crtp<simd_mapping<integral_t>, integral_t>
78{
79 static inline constexpr auto mm512_maskz_mov_epi = simde_mm512_maskz_mov_epi8;
80 static inline constexpr auto mm512_set1_epi = simde_mm512_set1_epi8;
81 static inline constexpr auto mm512_add_epi = simde_mm512_add_epi8;
82 static inline constexpr auto mm512_sub_epi = simde_mm512_sub_epi8;
83};
84
85// SIMD instructions for int16_t and uint16_t.
86template <std::integral integral_t>
87 requires (sizeof(integral_t) == 2)
88struct simd_mapping<integral_t> : simd_mapping_crtp<simd_mapping<integral_t>, integral_t>
89{
90 static inline constexpr auto mm512_maskz_mov_epi = simde_mm512_maskz_mov_epi16;
91 static inline constexpr auto mm512_set1_epi = simde_mm512_set1_epi16;
92 static inline constexpr auto mm512_add_epi = simde_mm512_add_epi16;
93 static inline constexpr auto mm512_sub_epi = simde_mm512_sub_epi16;
94};
95
96// SIMD instructions for int32_t and uint32_t.
97template <std::integral integral_t>
98 requires (sizeof(integral_t) == 4)
99struct simd_mapping<integral_t> : simd_mapping_crtp<simd_mapping<integral_t>, integral_t>
100{
101 static inline constexpr auto mm512_maskz_mov_epi = simde_mm512_maskz_mov_epi32;
102 static inline constexpr auto mm512_set1_epi = simde_mm512_set1_epi32;
103 static inline constexpr auto mm512_add_epi = simde_mm512_add_epi32;
104 static inline constexpr auto mm512_sub_epi = simde_mm512_sub_epi32;
105};
106
107// SIMD instructions for int64_t and uint64_t.
108template <std::integral integral_t>
109 requires (sizeof(integral_t) == 8)
110struct simd_mapping<integral_t> : simd_mapping_crtp<simd_mapping<integral_t>, integral_t>
111{
112 static inline constexpr auto mm512_maskz_mov_epi = simde_mm512_maskz_mov_epi64;
113 static inline constexpr auto mm512_set1_epi = simde_mm512_set1_epi64;
114 static inline constexpr auto mm512_add_epi = simde_mm512_add_epi64;
115 static inline constexpr auto mm512_sub_epi = simde_mm512_sub_epi64;
116};
118#endif
119
143template <std::integral value_t>
144class counting_vector : public std::vector<value_t, seqan::hibf::contrib::aligned_allocator<value_t, 64u>>
145{
146private:
149
150public:
154 counting_vector() = default;
155 counting_vector(counting_vector const &) = default;
159 ~counting_vector() = default;
160
161 using base_t::base_t;
163
172 {
173 impl<operation::add>(bit_vector);
174 return *this;
175 }
176
188 {
189 impl<operation::sub>(bit_vector);
190 return *this;
191 }
192
201 {
202 assert(this->size() >= rhs.size()); // The counting vector may be bigger than what we need.
203
204 std::transform(this->begin(), this->end(), rhs.begin(), this->begin(), std::plus<value_t>());
205
206 return *this;
207 }
208
214 {
215 assert(this->size() >= rhs.size()); // The counting vector may be bigger than what we need.
216
217 std::transform(this->begin(), this->end(), rhs.begin(), this->begin(), std::minus<value_t>());
218
219 return *this;
220 }
221
222private:
224 enum class operation : uint8_t
225 {
226 add,
227 sub
228 };
229
230 template <size_t multiple>
231 inline constexpr size_t next_multiple_of(size_t const value) noexcept
232 {
233 if constexpr (multiple == 8u)
234 return ((value + 7u) >> 3) << 3;
235 else if constexpr (multiple == 16u)
236 return ((value + 15u) >> 4) << 4;
237 else if constexpr (multiple == 32u)
238 return ((value + 31u) >> 5) << 5;
239 else if constexpr (multiple == 64u)
240 return ((value + 63u) >> 6) << 6;
241 else
242 static_assert("multiple must be one of 8, 16, 32, 64.");
243 }
244
246 template <operation op>
247 inline void impl(bit_vector const & bit_vector)
248 {
249 // The counting vector may be bigger than what we need, but not smaller.
250 if (this->size() < bit_vector.size())
251 seqan::hibf::unreachable();
252#if HIBF_HAS_AVX512
253 // AVX512BW: mm512_maskz_mov_epi, mm512_add_epi
254 // AVX512F: mm512_set1_epi, _mm512_load_si512, _mm512_store_si512
255 using simd = simd_mapping<value_t>;
256 using bits_type = typename simd::bits_type;
257
258 bits_type const * bit_vector_ptr = reinterpret_cast<bits_type const *>(bit_vector.data());
259 value_t * counting_vector_ptr = base_t::data();
260
261 size_t const bits = next_multiple_of<simd::bits_per_iterations>(bit_vector.size());
262 assert(bits <= this->capacity()); // Not enough memory reserved for AVX512 chunk access.
263 size_t const iterations = bits / simd::bits_per_iterations;
264
265 for (size_t iteration = 0; iteration < iterations;
266 ++iteration, ++bit_vector_ptr, counting_vector_ptr += simd::bits_per_iterations)
267 {
268 simde__m512i load = simde_mm512_load_si512(counting_vector_ptr);
269 if constexpr (op == operation::add)
270 {
271 load = simd::mm512_add_epi(load, simd::expand_bits(bit_vector_ptr));
272 }
273 else
274 {
275 load = simd::mm512_sub_epi(load, simd::expand_bits(bit_vector_ptr));
276 }
277 simde_mm512_store_si512(counting_vector_ptr, load);
278 }
279#else
280 size_t const words = divide_and_ceil(bit_vector.size(), 64u);
281 uint64_t const * const bit_vector_ptr = bit_vector.data();
282
283 // Jump to the next 1 and return the number of jumped bits in value.
284 auto jump_to_next_1bit = [](uint64_t & value)
285 {
286 auto const zeros = std::countr_zero(value);
287 value >>= zeros; // skip number of zeros
288 return zeros;
289 };
290
291 // Each iteration can handle 64 bits, i.e., one word.
292 for (size_t iteration = 0; iteration < words; ++iteration)
293 {
294 uint64_t current_word = bit_vector_ptr[iteration];
295
296 // For each set bit in the current word, add/subtract 1 to the corresponding bin.
297 for (size_t bin = iteration * 64u; current_word != 0u; ++bin, current_word >>= 1)
298 {
299 // Jump to the next 1
300 bin += jump_to_next_1bit(current_word);
301
302 if constexpr (op == operation::add)
303 {
304 ++(*this)[bin];
305 }
306 else
307 {
308 --(*this)[bin];
309 }
310 }
311 }
312#endif
313 }
314};
315
316} // namespace seqan::hibf
Provides seqan::hibf::bit_vector.
An bit vector.
Definition bit_vector.hpp:66
A data structure that behaves like a std::vector and can be used to consolidate the results of multip...
Definition counting_vector.hpp:145
counting_vector & operator+=(counting_vector const &rhs)
Bin-wise addition of two seqan::hibf::counting_vectors.
Definition counting_vector.hpp:200
counting_vector & operator+=(bit_vector const &bit_vector)
Bin-wise adds the bits of a seqan::hibf::bit_vector.
Definition counting_vector.hpp:171
counting_vector(counting_vector const &)=default
Defaulted.
counting_vector & operator-=(bit_vector const &bit_vector)
Bin-wise subtracts the bits of a seqan::hibf::bit_vector.
Definition counting_vector.hpp:187
counting_vector & operator-=(counting_vector const &rhs)
Bin-wise subtraction of two seqan::hibf::counting_vectors.
Definition counting_vector.hpp:213
~counting_vector()=default
Defaulted.
counting_vector & operator=(counting_vector &&)=default
Defaulted.
counting_vector(counting_vector &&)=default
Defaulted.
counting_vector & operator=(counting_vector const &)=default
Defaulted.
counting_vector()=default
Defaulted.
T countr_zero(T... args)
constexpr size_t divide_and_ceil(t1 const dividend, t2 const divisor) noexcept
Returns, for unsigned integral operands, dividend / divisor ceiled to the next integer value.
Definition divide_and_ceil.hpp:21
T is_base_of_v
Provides platform and dependency checks.
T transform(T... args)
Provides seqan::hibf::unreachable.