Zip and Skip Tries
Loading...
Searching...
No Matches
BitString.cuh
Go to the documentation of this file.
1
10#pragma once
11
12#include <array>
13#include <bit> // for std::countl_zero, std::bit_ceil
14#include <compare> // for std::strong_ordering and <=> operator
15#include <cstddef> // for size_t
16#include <cstdint> // for uintmax_t
17#include <numeric> // Provides std::min (for two arguments)
18#include <vector>
19#include <algorithm> // Include for std::min/max
20
21// For file I/O
22#include <fstream>
23#include <stdexcept> // for std::runtime_error
24#include <string>
25
26#include <iostream>
27
28// Include CUDA headers for parallel operations
29#include "msw.cuh"
30#include "cuda_utils.cuh"
31
40static constexpr size_t MIN_PAR_COMPARE_WORD_SIZE = 1;
41
56template<typename CHAR_T, unsigned CHAR_SIZE_BITS = sizeof(CHAR_T) * 8>
58{
59public:
61 static constexpr unsigned WORD_SIZE_BITS = sizeof(uintmax_t) * 8;
63 static constexpr unsigned ALPHA = WORD_SIZE_BITS / CHAR_SIZE_BITS;
66
73 explicit BitString(const CHAR_T* data, size_t size);
74
85 template<typename Container>
86 explicit BitString(const Container& data);
87
93 explicit BitString(const std::string& data)
94 : BitString(reinterpret_cast<const CHAR_T*>(data.c_str()), data.size()) // Reinterpret cast assumes CHAR_T layout matches char
95 {
96 // Body intentionally empty
97 }
98
103 BitString() = default;
104
111 void push_back(CHAR_T c) noexcept;
112
123 CHAR_T operator[](size_t index) const;
124
129 size_t size() const noexcept { return m_size; }
130
135 size_t word_count() const noexcept { return m_data.size(); }
136
141 void clear() noexcept
142 {
143 m_data.clear();
144 m_size = 0;
145 }
146
151 {
152 std::strong_ordering result;
153 size_t lcp;
154 };
155
174 ResultLCP seq_k_compare(const BitString& other, size_t lcp, size_t max_compare) const noexcept;
175
187 ResultLCP seq_k_compare(const BitString& other, size_t lcp) const noexcept
188 {
189 return seq_k_compare(other, lcp, size());
190 }
191
219 ResultLCP par_k_compare(const BitString& other, size_t lcp, size_t max_compare, uintmax_t* d_a, uintmax_t* d_largeblock, size_t& max_copied) const noexcept;
220
236 ResultLCP par_k_compare(const BitString& other, size_t lcp, uintmax_t* d_a, uintmax_t* d_largeblock, size_t& max_copied) const noexcept
237 {
238 return par_k_compare(other, lcp, size(), d_a, d_largeblock, max_copied);
239 }
240
246 const uintmax_t* data() const noexcept { return m_data.data(); }
247
248private:
250 std::vector<uintmax_t> m_data;
252 size_t m_size = 0;
253
262 static constexpr std::array<unsigned, ALPHA> GET_SHIFTS();
264 static constexpr std::array<unsigned, ALPHA> m_shifts = GET_SHIFTS();
265
274 static constexpr std::array<uintmax_t, ALPHA> GET_MASKS();
276 static constexpr std::array<uintmax_t, ALPHA> m_masks = GET_MASKS();
277
287 static constexpr CHAR_T GET_CHAR(uintmax_t word, unsigned bit_index);
288
289public:
290 // Forward declaration for the friend function template
291 template<typename CT, unsigned CSB>
292 friend std::ostream& operator<<(std::ostream& os, const BitString<CT, CSB>& bs);
293
303 void print_bytes(std::ostream& os) const;
304
314 void to_file(const std::string& filename) const;
315
324 void to_file(std::ofstream& file) const;
325
336 static BitString from_file(const std::string& filename);
337
347 static BitString from_file(std::ifstream& file);
348
355 std::string to_string() const noexcept;
356
357public:
365 {
366 public:
367 using iterator_category = std::forward_iterator_tag;
368 using value_type = CHAR_T;
369 using difference_type = std::ptrdiff_t;
370 using pointer = const CHAR_T*; // Or void if value is returned by copy
371 using reference = CHAR_T; // Return by value
372
378 Iterator(const BitString& bs, size_t index)
379 : m_bs(bs), m_index(index)
380 {
381 // Body intentionally empty
382 }
383
388 CHAR_T operator*() const noexcept
389 {
390 return m_bs[m_index];
391 }
392
398 {
399 ++m_index;
400 return *this;
401 }
402
408 Iterator operator++(int) noexcept
409 {
410 Iterator temp = *this;
411 ++(*this);
412 return temp;
413 }
414
415
421 bool operator==(const Iterator& other) const noexcept
422 {
423 return m_index == other.m_index;
424 }
425
426 private:
430 size_t m_index;
431 };
432
437 Iterator begin() const noexcept
438 {
439 return Iterator(*this, 0);
440 }
441
446 Iterator end() const noexcept
447 {
448 return Iterator(*this, size());
449 }
450};
451
452// == Implementation of Template Methods ==
453
454template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
455constexpr std::array<unsigned, BitString<CHAR_T, CHAR_SIZE_BITS>::ALPHA> BitString<CHAR_T, CHAR_SIZE_BITS>::GET_SHIFTS()
456{
457 std::array<unsigned, ALPHA> shifts;
458 for (unsigned i = 0; i < ALPHA; ++i)
459 {
460 shifts[ALPHA - i - 1] = i * CHAR_SIZE_BITS;
461 }
462 return shifts;
463}
464
465template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
466constexpr std::array<uintmax_t, BitString<CHAR_T, CHAR_SIZE_BITS>::ALPHA> BitString<CHAR_T, CHAR_SIZE_BITS>::GET_MASKS()
467{
468 uintmax_t mask = 0;
469 for (unsigned i = 0; i < CHAR_SIZE_BITS; ++i)
470 {
471 mask |= static_cast<uintmax_t>(1) << i;
472 }
473
474 std::array<uintmax_t, ALPHA> masks;
475 for (unsigned i = 0; i < ALPHA; ++i)
476 {
477 masks[i] = mask << m_shifts[i];
478 }
479 return masks;
480}
481
482template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
483constexpr CHAR_T BitString<CHAR_T, CHAR_SIZE_BITS>::GET_CHAR(uintmax_t word, unsigned bit_index)
484{
485 const uintmax_t masked_word = word & m_masks[bit_index];
486 const uintmax_t shifted_word = masked_word >> m_shifts[bit_index];
487 return static_cast<CHAR_T>(shifted_word);
488}
489
490template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
491BitString<CHAR_T, CHAR_SIZE_BITS>::BitString(const CHAR_T* data, size_t data_size)
492 : m_size(data_size)
493{
494 const size_t word_count = (size() + ALPHA - 1) / ALPHA;
495 m_data.resize(word_count, 0);
496
497 for (size_t i = 0; i < size(); ++i)
498 {
499 const size_t word_index = i / ALPHA;
500 const size_t bit_index = i % ALPHA;
501 const uintmax_t mask = static_cast<uintmax_t>(data[i]) << m_shifts[bit_index];
502 m_data[word_index] |= mask;
503 }
504}
505
506template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
507template<typename Container>
509 : m_size(data.size()) // Get size from the container
510{
511 const size_t word_count = (size() + ALPHA - 1) / ALPHA;
512 m_data.resize(word_count, 0);
513
514 for (size_t i = 0; i < size(); ++i)
515 {
516 const size_t word_index = i / ALPHA;
517 const size_t bit_index = i % ALPHA;
518 const uintmax_t mask = static_cast<uintmax_t>(data[i]) << m_shifts[bit_index];
519 m_data[word_index] |= mask;
520 }
521}
522
523template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
525{
526 const size_t word_index = size() / ALPHA;
527 const size_t bit_index = size() % ALPHA;
528 const uintmax_t mask = static_cast<uintmax_t>(c) << m_shifts[bit_index];
529
530 if (word_index >= m_data.size())
531 {
532 m_data.push_back(0);
533 }
534
535 m_data[word_index] |= mask;
536 ++m_size;
537}
538
539template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
541{
542 return GET_CHAR(m_data[index / ALPHA], index % ALPHA);
543}
544
545template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
546auto BitString<CHAR_T, CHAR_SIZE_BITS>::seq_k_compare(const BitString& other, size_t lcp, size_t max_compare) const noexcept -> ResultLCP
547{
548 const size_t min_size = std::min(size(), other.size());
549 const size_t max_compare_until = lcp + max_compare;
550 size_t word_index = lcp / ALPHA;
551
552 lcp -= lcp % ALPHA; // Reset to word boundary
553
554 while (lcp < min_size && lcp < max_compare_until)
555 {
556 const uintmax_t XOR = m_data[word_index] ^ other.m_data[word_index];
557
558 if (XOR == 0)
559 {
560 lcp += ALPHA;
561 ++word_index;
562 continue;
563 }
564
565 const size_t l_zero = std::countl_zero(XOR);
566
567 lcp += l_zero / CHAR_SIZE_BITS;
568
569 const size_t bit_index = lcp % ALPHA;
570
571 // Mismatch found within limit
572 const CHAR_T lhs = GET_CHAR(m_data[word_index], bit_index);
573 const CHAR_T rhs = GET_CHAR(other.m_data[word_index], bit_index);
574
575 return { lhs <=> rhs, lcp };
576 }
577
578 if (lcp >= min_size)
579 {
580 return { size() <=> other.size(), min_size };
581 }
582
583 return { std::strong_ordering::equal, lcp };
584}
585
586template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
587auto BitString<CHAR_T, CHAR_SIZE_BITS>::par_k_compare(const BitString& other, size_t lcp, size_t max_compare, uintmax_t* d_a, uintmax_t* d_largeblock, size_t& max_copied) const noexcept -> ResultLCP
588{
589 // Fallback for small comparisons
590 if (max_compare <= MIN_PAR_COMPARE_CHAR_SIZE)
591 {
592 return seq_k_compare(other, lcp, max_compare);
593 }
594
595 const size_t min_size = std::min(size(), other.size());
596 const size_t min_word_count = std::min(word_count(), other.word_count());
597 size_t word_index = lcp / ALPHA;
598
599 // Calculate how many full words remain to potentially compare
600 const size_t num_remaining_words = (min_word_count > word_index) ? (min_word_count - word_index) : 0;
601
602 // Calculate max words to compare based on max_compare characters
603 // Need to compare up to the word containing character (lcp + max_compare - 1)
604 const size_t end_char_index = lcp + max_compare;
605 const size_t end_word_index = (end_char_index + ALPHA - 1) / ALPHA; // Word index containing the last char to check
606 const size_t max_compare_words = (end_word_index > word_index) ? (end_word_index - word_index) : 0;
607
608
609 const size_t actual_max_compare_words = std::min(max_compare_words, num_remaining_words);
610
611 // Fallback if the actual number of words to compare is too small
612 if (actual_max_compare_words <= MIN_PAR_COMPARE_WORD_SIZE)
613 {
614 return seq_k_compare(other, lcp, max_compare);
615 }
616
617 // Ensure data is on the device
618 if (max_copied < word_index + actual_max_compare_words)
619 {
620 size_t end_copy_index = word_index + actual_max_compare_words;
621
622 // get next power of 2 greater than or equal to end_copy_index
623 size_t end_copy = std::min(std::bit_ceil(end_copy_index), word_count());
624 if (end_copy > max_copied) {
625 copy_to_device(d_a + max_copied, data() + max_copied, end_copy - max_copied);
626 max_copied = end_copy;
627 }
628 }
629
630 lcp -= lcp % ALPHA; // Reset LCP to the start of the current word boundary for parallel comparison
631
632 // Perform parallel mismatch search
633 auto msw = par_find_mismatch(d_a + word_index, other.data() + word_index, d_largeblock, actual_max_compare_words);
634
635 // Calculate LCP based on mismatch word index (msw)
636 lcp += msw * ALPHA;
637
638 // If no mismatch found within the compared words
639 if (msw == actual_max_compare_words)
640 {
641 // Check if the comparison reached the end of either string
642 if (lcp >= min_size)
643 {
644 return { size() <=> other.size(), min_size };
645 }
646
647 return { std::strong_ordering::equal, lcp };
648 }
649
650 // Mismatch found by parallel kernel. Refine LCP within the mismatch word.
651 word_index = lcp / ALPHA; // Update word_index to the mismatch word
652
653 // Find exact mismatch character within the word
654 const uintmax_t XOR = m_data[word_index] ^ other.m_data[word_index];
655 const size_t l_zero = std::countl_zero(XOR);
656
657 lcp += l_zero / CHAR_SIZE_BITS;
658
659 const size_t bit_index = lcp % ALPHA;
660
661 // Mismatch is within the limit
662 const CHAR_T lhs = GET_CHAR(m_data[word_index], bit_index);
663 const CHAR_T rhs = GET_CHAR(other.m_data[word_index], bit_index);
664
665 return { lhs <=> rhs, lcp };
666}
667
668template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
670{
671 static constexpr unsigned shift = WORD_SIZE_BITS - 8;
672
673 for (size_t i = 0, a = 0; i + ALPHA <= size(); i += ALPHA, ++a)
674 {
675 uintmax_t word = m_data[a];
676
677 for (unsigned c = 0; c < WORD_SIZE_BITS / 8; c++, word <<= 8)
678 {
679 os << static_cast<uint8_t>(word >> shift);
680 }
681 }
682
683 // Handle last partial word if it exists
684 if (size() % ALPHA != 0 && !m_data.empty())
685 {
686 uintmax_t word = m_data.back();
687 unsigned remaining_chars = size() % ALPHA;
688 unsigned remaining_bits = remaining_chars * CHAR_SIZE_BITS;
689 unsigned bytes_to_print = (remaining_bits + 7) / 8; // Round up bytes
690
691 // Process the required bytes from the most significant side
692 for (unsigned byte_idx = 0; byte_idx < bytes_to_print; ++byte_idx)
693 {
694 os << static_cast<uint8_t>(word >> (shift - byte_idx * 8));
695 }
696 }
697}
698
699
709template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
710std::ostream& operator<<(std::ostream& os, const BitString<CHAR_T, CHAR_SIZE_BITS>& bs)
711{
712 for (CHAR_T c : bs)
713 {
714 os << c;
715 }
716 return os;
717}
718
719template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
720void BitString<CHAR_T, CHAR_SIZE_BITS>::to_file(std::ofstream& file) const
721{
722 file.write(reinterpret_cast<const char*>(&m_size), sizeof(size_t));
723 file.write(reinterpret_cast<const char*>(m_data.data()), m_data.size() * sizeof(uintmax_t));
724}
725
726template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
727void BitString<CHAR_T, CHAR_SIZE_BITS>::to_file(const std::string& filename) const
728{
729 std::ofstream file(filename, std::ios::binary);
730 if (!file)
731 {
732 throw std::runtime_error("Failed to open file for writing: " + filename);
733 }
734 to_file(file);
735 // file automatically closed by destructor
736}
737
738template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
740{
742
743 file.read(reinterpret_cast<char*>(&bs.m_size), sizeof(size_t));
744 if (!file) { // Check read success
745 throw std::runtime_error("Failed to read size from file stream.");
746 }
747
748 const size_t word_count = (bs.size() + ALPHA - 1) / ALPHA;
749 bs.m_data.resize(word_count, 0); // Resize before reading into it
750
751 if (word_count > 0) { // Only read if there's data expected
752 file.read(reinterpret_cast<char*>(bs.m_data.data()), word_count * sizeof(uintmax_t));
753 if (!file) { // Check read success
754 throw std::runtime_error("Failed to read data from file stream.");
755 }
756 }
757
758
759 return bs;
760}
761
762template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
764{
765 std::ifstream file(filename, std::ios::binary);
766 if (!file)
767 {
768 throw std::runtime_error("Failed to open file for reading: " + filename);
769 }
770 return from_file(file);
771 // file automatically closed by destructor
772}
773
774template<typename CHAR_T, unsigned CHAR_SIZE_BITS>
776{
777 std::string str;
778 str.reserve(size());
779
780 for (CHAR_T c : *this)
781 {
782 // Assumes CHAR_T is convertible to char, potential truncation if wider.
783 str.push_back(static_cast<char>(c));
784 }
785
786 return str;
787}
static constexpr size_t MIN_PAR_COMPARE_WORD_SIZE
Minimum number of words required to trigger parallel comparison.
Definition BitString.cuh:40
std::ostream & operator<<(std::ostream &os, const BitString< CHAR_T, CHAR_SIZE_BITS > &bs)
Overloaded stream insertion operator for printing BitString content.
An iterator class for traversing the characters within a BitString.
bool operator==(const Iterator &other) const noexcept
Compares this iterator with another for equality.
const BitString & m_bs
Reference to the BitString being iterated over.
std::ptrdiff_t difference_type
std::forward_iterator_tag iterator_category
size_t m_index
Current character index.
const CHAR_T * pointer
Iterator & operator++() noexcept
Pre-increments the iterator to point to the next character.
Iterator operator++(int) noexcept
Post-increments the iterator.
Iterator(const BitString &bs, size_t index)
Constructs an iterator pointing to a specific character index.
CHAR_T operator*() const noexcept
Dereferences the iterator to get the character at the current position.
A class to store strings compactly into integers, packing multiple characters into a single word.
Definition BitString.cuh:58
size_t word_count() const noexcept
Returns the number of uintmax_t words used for storage.
void to_file(std::ofstream &file) const
Writes the BitString contents to an already open output file stream.
void print_bytes(std::ostream &os) const
Prints the raw byte representation of the BitString to an output stream.
void to_file(const std::string &filename) const
Writes the BitString contents to a file in binary format.
CHAR_T operator[](size_t index) const
Accesses the character at a specific index.
static BitString from_file(std::ifstream &file)
Reads a BitString from an already open input file stream.
size_t size() const noexcept
Returns the number of characters stored in the BitString.
BitString()=default
Default constructor.
Iterator end() const noexcept
Returns an iterator pointing past the end of the BitString.
void clear() noexcept
Clears the contents of the BitString, making it empty.
BitString(const CHAR_T *data, size_t size)
Constructs a BitString object from a raw character array.
void push_back(CHAR_T c) noexcept
Appends a single character to the end of the BitString.
friend std::ostream & operator<<(std::ostream &os, const BitString< CT, CSB > &bs)
static constexpr std::array< unsigned, ALPHA > m_shifts
Compile-time generated array of bit shift amounts.
static constexpr std::array< unsigned, ALPHA > GET_SHIFTS()
Generates the bit shift amounts needed for character extraction/insertion at compile time.
size_t m_size
The number of characters stored in the BitString.
static constexpr unsigned ALPHA
Number of characters that can be packed into a single storage word.
Definition BitString.cuh:63
std::vector< uintmax_t > m_data
Internal storage vector holding the packed character data.
ResultLCP par_k_compare(const BitString &other, size_t lcp, size_t max_compare, uintmax_t *d_a, uintmax_t *d_largeblock, size_t &max_copied) const noexcept
Performs parallel comparison (potentially using CUDA) with another BitString, starting from a given L...
Iterator begin() const noexcept
Returns an iterator pointing to the beginning of the BitString.
static constexpr CHAR_T GET_CHAR(uintmax_t word, unsigned bit_index)
Extracts a single character from a given word at a specific bit index (sub-word position).
BitString(const Container &data)
Constructs a BitString object from a container of characters.
static constexpr std::array< uintmax_t, ALPHA > m_masks
Compile-time generated array of bit masks.
ResultLCP par_k_compare(const BitString &other, size_t lcp, uintmax_t *d_a, uintmax_t *d_largeblock, size_t &max_copied) const noexcept
Performs parallel comparison (potentially using CUDA) with another BitString, starting from a given L...
static BitString from_file(const std::string &filename)
Reads a BitString from a file in binary format.
ResultLCP seq_k_compare(const BitString &other, size_t lcp, size_t max_compare) const noexcept
Performs sequential comparison with another BitString, starting from a given LCP offset,...
static constexpr std::array< uintmax_t, ALPHA > GET_MASKS()
Generates the bit masks needed for character extraction at compile time.
std::string to_string() const noexcept
Converts the BitString back to a standard std::string.
const uintmax_t * data() const noexcept
Provides direct access to the underlying packed data array.
static constexpr unsigned WORD_SIZE_BITS
Size of the underlying storage word (uintmax_t) in bits.
Definition BitString.cuh:61
BitString(const std::string &data)
Constructs a BitString object from a std::string.
Definition BitString.cuh:93
static constexpr size_t MIN_PAR_COMPARE_CHAR_SIZE
Minimum number of characters required to trigger parallel comparison. Derived from MIN_PAR_COMPARE_WO...
Definition BitString.cuh:65
ResultLCP seq_k_compare(const BitString &other, size_t lcp) const noexcept
Performs sequential comparison with another BitString, starting from a given LCP offset.
Provides utility functions and classes for CUDA operations.
T * copy_to_device(T *d_arr, const T *arr, size_t size)
Copies data from host memory to existing device memory.
size_t par_find_mismatch(const uintmax_t *const d_a, const uintmax_t *const b, uintmax_t *d_large_block, size_t n)
GPU-accelerated implementation of finding the first mismatching word with pre-allocated memory.
Definition msw.cu:178
CUDA-accelerated Most Significant Word finding algorithms.
Structure to hold the result of a comparison, including the ordering and the length of the common pre...
size_t lcp
The length of the longest common prefix in characters.
std::strong_ordering result
The comparison result (less, greater, equal).