Zip and Skip Tries
Loading...
Searching...
No Matches
Functions
msw.cu File Reference

Implementation of CUDA-accelerated Most Significant Word finding algorithms. More...

#include <bit>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <stdio.h>
#include "cuda_utils.cuh"
Include dependency graph for msw.cu:

Go to the source code of this file.

Functions

__global__ void par_sig_sqrt (const uintmax_t *const p1, uintmax_t *p2, uintmax_t *sig_out, size_t n)
 CUDA kernel that implements the first phase of the square root decomposition.
 
__global__ void naive_leftmost_prisoner (uintmax_t *p, size_t n)
 Parallel kernel to find the leftmost non-zero element in an array.
 
__global__ void par_get_section_diff (uintmax_t *tree, uintmax_t *section, size_t n)
 Parallel kernel to identify and record the index of the first non-zero element.
 
size_t seq_find_mismatch (const uintmax_t *const arr1, const uintmax_t *const arr2, size_t size)
 Sequential implementation of finding the first mismatching word between two arrays.
 
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.
 
uintmax_talloc_large_block_to_device (size_t n)
 Allocates a large block of device memory for parallel mismatch operations.
 
size_t par_find_mismatch (const uintmax_t *const a, const uintmax_t *const b, size_t n)
 Convenience wrapper for GPU-accelerated mismatch finding.
 

Detailed Description

Implementation of CUDA-accelerated Most Significant Word finding algorithms.

This file implements a parallel algorithm for finding the first mismatching word between two arrays using a two-phase square root decomposition approach:

Phase 1: Divide the arrays into sqrt(n) chunks and identify which chunk contains the first mismatch. Phase 2: Within the identified chunk, find the exact position of the first mismatch.

The implementation uses three key CUDA kernels:

This algorithm achieves O(n) work with O(1) span when sufficient threads are available, making it highly efficient for large arrays on GPU hardware.

See also
msw.cuh
cuda_utils.cuh

Definition in file msw.cu.

Function Documentation

◆ alloc_large_block_to_device()

uintmax_t * alloc_large_block_to_device ( size_t  n)

Allocates a large block of device memory for parallel mismatch operations.

Calculates the required memory size based on the input size n and allocates a contiguous block of memory on the device. The size is calculated to accommodate:

  • A copy of the second array (n elements)
  • Storage for the result (1 element)
  • The signature array for chunk identification (sqrt(n) elements)
  • An extra element for safety (1 element)
Parameters
nNumber of elements to be processed
Returns
Pointer to allocated device memory of size n + sqrt(n-1) + 2

Definition at line 256 of file msw.cu.

257{
258 size_t large_block_size = n + std::sqrt(n - 1) + 2;
259
261}
T * alloc_to_device(size_t size)
Allocates memory on the CUDA device.

◆ naive_leftmost_prisoner()

__global__ void naive_leftmost_prisoner ( uintmax_t p,
size_t  n 
)

Parallel kernel to find the leftmost non-zero element in an array.

Uses a tournament-style approach where each pair of elements is compared, and if a left element (lower index) is non-zero, it "eliminates" the right element by setting it to zero. After all comparisons, only the leftmost non-zero element remains. This kernel achieves O(n²) work in O(1) span when sufficient threads are available.

The algorithm distributes all n-choose-2 pairwise comparisons across available threads.

Definition at line 68 of file msw.cu.

69{
70 // Calculate total number of pairwise comparisons needed
71 size_t n_choose_2 = n * (n - 1) / 2;
72
73 // Distribute comparisons evenly across threads
74 size_t num_computations = (n_choose_2 - 1) / get_num_threads() + 1;
75
76 // Calculate starting comparison index for this thread
77 auto i = get_tid() * num_computations;
78
79 // Convert linear index i to pair (a,b) using inverse of triangular number formula
80 // This maps from a 1D index to a 2D comparison between elements at positions a and b
81 size_t b = (1 + std::sqrt(1 + 8 * i)) / 2; // Row index calculation
82 size_t a = i - b * (b - 1) / 2; // Column index calculation
83
84 // Process this thread's allocated comparisons
85 while (num_computations-- && b < n)
86 {
87 // If the left element is non-zero, eliminate the right element
88 if (p[a]) p[b] = 0;
89
90 // Move to next comparison pair
91 ++a;
92
93 // If we reach the end of a row, move to the next row
94 if (a == b)
95 {
96 a = 0; // Start at the leftmost element
97 ++b; // Move to the next row
98 }
99 }
100}
constexpr size_t get_num_threads()
Gets the total number of threads in the kernel launch (compile-time).
__device__ __forceinline__ size_t get_tid()
Gets the global thread ID within a CUDA kernel launch.

◆ par_find_mismatch() [1/2]

size_t par_find_mismatch ( const uintmax_t *const  a,
const uintmax_t *const  b,
size_t  n 
)

Convenience wrapper for GPU-accelerated mismatch finding.

GPU-accelerated implementation of finding the first mismatching word.

This function handles all the device memory management, including:

  • Allocating device memory for both arrays
  • Copying the first array to the device
  • Calling the core implementation
  • Freeing all allocated device memory

This provides a simpler interface for callers who don't need to manage device memory themselves or reuse memory across multiple calls.

Parameters
aFirst input array (host memory)
bSecond input array (host memory)
nNumber of elements in the arrays
Returns
Index of the first mismatching word, or n if arrays are identical

Definition at line 280 of file msw.cu.

281{
282 uintmax_t *d_a = copy_to_device(a, n);
284
285 size_t result = par_find_mismatch(d_a, b, d_large_block, n);
286
287 device_free(d_a);
289
290 return result;
291}
void device_free(T *d_arr)
Frees memory on the CUDA device.
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
uintmax_t * alloc_large_block_to_device(size_t n)
Allocates a large block of device memory for parallel mismatch operations.
Definition msw.cu:256

◆ par_find_mismatch() [2/2]

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.

GPU-accelerated mismatch finding with pre-allocated device memory.

This function implements a two-phase square root decomposition algorithm:

  1. Divide the arrays into sqrt(n) chunks and identify which chunk contains the first mismatch
  2. Within the identified chunk, find the exact position of the first mismatch

The algorithm uses three CUDA kernels:

  • par_sig_sqrt: Marks chunks containing mismatches
  • naive_leftmost_prisoner: Eliminates all but the leftmost non-zero element
  • par_get_section_diff: Identifies the index of the first non-zero element

Time complexity: O(n) work with O(1) span when sufficient threads are available Space complexity: O(n + sqrt(n)) for temporary storage

Precondition
d_a is already populated on the device
d_large_block has size at least n + sqrt(n) + 2 to hold b and signature arrays
Parameters
d_aFirst input array (already in device memory)
bSecond input array (host memory, will be copied to device)
d_large_blockPre-allocated device memory block for temporary storage
nNumber of elements in the arrays
Returns
Index of the first mismatching word, or n if arrays are identical

Definition at line 178 of file msw.cu.

179{
180 // Copy the second array to device memory using pre-allocated buffer
182
183 // Set up memory pointers within the large block:
184 // - d_section: single value to store result
185 // - d_sqrt: array of size sqrt(n) to track which chunks have mismatches
188 size_t num_sqrt = std::sqrt(n - 1) + 1; // Calculate number of sqrt-sized chunks
189
190 // Initialize the section result to max value (no match found yet)
192
193 // Initialize the sqrt array to zeros (no mismatches found yet)
195
196 // PHASE 1: Find which sqrt(n)-sized chunk has the first mismatch
197 // For each element, compute XOR of the two arrays and mark its chunk in d_sqrt if mismatch
199
200 // Find the first non-zero chunk (chunk with a mismatch) in parallel
202
203 // Extract the index of the first chunk with a mismatch
205
206 // Copy the result back to host memory
208 size_t section = *h_section;
209
210 // If no mismatch found, return n (indicating arrays are identical)
211 if (section == std::numeric_limits<uintmax_t>::max())
212 {
213 delete[] h_section;
214 return n;
215 }
216
217 // Reset the section result for the second phase
219
220 // PHASE 2: Find the exact mismatch position within the identified chunk
221 // Calculate the offset and size of the identified chunk
223 size_t section_end = std::min(n, section_offset + num_sqrt);
226
227 // Find the first non-zero word within the chunk (containing XOR results)
229
230 // Extract the index of the first mismatching word within the chunk
232
233 // Copy the result back to host memory
235 size_t result = *h_section;
236
237 delete[] h_section;
238
239 // Return the global index of the first mismatch (chunk offset + position within chunk)
240 return section_offset + result;
241}
T * memset_within_device(T *d_arr, size_t size, int value)
Sets a region of device memory to a specific byte value.
T * copy_from_device(const T *d_arr, size_t size)
Allocates host memory and copies data from device memory to it.

◆ par_get_section_diff()

__global__ void par_get_section_diff ( uintmax_t tree,
uintmax_t section,
size_t  n 
)

Parallel kernel to identify and record the index of the first non-zero element.

After the naive_leftmost_prisoner kernel has eliminated all non-leftmost elements, this kernel scans the array to find the remaining non-zero element and records its index. Multiple threads may write to the same location, but they'll all write the same value since only one element should remain non-zero after the elimination process.

This kernel is used in both phases of the square root algorithm:

  1. To find which sqrt(n)-sized chunk contains the first mismatch
  2. To find the exact position within that chunk

Definition at line 113 of file msw.cu.

114{
115 // Each thread checks a subset of elements
116 for (auto i = get_tid(); i < n; i += get_num_threads())
117 {
118 // If we find a non-zero element, it must be the leftmost one after elimination
119 if (tree[i] != 0)
120 {
121 *section = i; // Record its index as the result
122 }
123 }
124}

◆ par_sig_sqrt()

__global__ void par_sig_sqrt ( const uintmax_t *const  p1,
uintmax_t p2,
uintmax_t sig_out,
size_t  n 
)

CUDA kernel that implements the first phase of the square root decomposition.

This kernel performs two critical operations:

  1. Computes the XOR (as inequality check) between corresponding elements of two arrays
  2. Maps each mismatch to its respective sqrt(n)-sized chunk in the signature array

Each thread processes multiple elements based on its thread ID. When a mismatch is found, the corresponding chunk in sig_out is marked with a 1.

Definition at line 41 of file msw.cu.

41 {
42 // Calculate number of chunks, each of size sqrt(n)
43 size_t num_sqrt = std::sqrt(n - 1) + 1;
44
45 // Each thread processes elements at positions (threadIdx + k*blockDim*gridDim)
46 for (auto i = get_tid(); i < n; i += get_num_threads())
47 {
48 // Store XOR result in p2 - non-zero values indicate mismatches
49 p2[i] = p1[i] != p2[i];
50
51 // If there's a mismatch, mark its chunk in the signature array
52 if (p2[i] != 0)
53 {
54 sig_out[i / num_sqrt] = 1;
55 }
56 }
57}

◆ seq_find_mismatch()

size_t seq_find_mismatch ( const uintmax_t *const  arr1,
const uintmax_t *const  arr2,
size_t  size 
)

Sequential implementation of finding the first mismatching word between two arrays.

This is a simple linear scan through both arrays, comparing corresponding elements until a mismatch is found. This function serves as a baseline for comparison with the parallel implementation and is used for validation.

Time complexity: O(n) in the worst case Space complexity: O(1)

Parameters
arr1First input array
arr2Second input array
sizeNumber of elements in the arrays
Returns
Index of the first mismatching word, or size if arrays are identical

Definition at line 141 of file msw.cu.

142{
143 for (size_t i = 0; i < size; ++i)
144 {
145 if (arr1[i] != arr2[i])
146 {
147 return i;
148 }
149 }
150
151 return size;
152}