I have a long array of float2's in global memory from which each block of my kernel reads 512 continuous values in each chunk (there is no discernible pattern to the specific chunk any given block accesses however). So I was thinking that I should be able to coalesce those 512 float2 loads from global memory. In an attempt to do that, I load the pointer to the first element out of the 512 into shared memory and then have each thread increment the pointer by its threadID to read the float2 from that address. However, this doesn't seem to be coalescing the reads when I check with Nsight Compute. I've marked the lines below where it says I have uncoalesced global accesses.
Along with sequential access, the best practices guide mentions alignment requirement but I am using a vector type, which the guide says are automatically aligned (in addition to them corresponding to 8 bytes per value). So I'm not sure why my reads aren't coalesced. The ratio of real to ideal global L2 sectors is around 1.12 for the read at the top and 1.09 for the bottom, which I'm assuming aren't that bad, but I still want to optimize those reads if possible.
One slight complication in my code that I didn't mention above is that each of those 512 reads are done nsegs times in a loop but I'm not sure if that would affect the read patterns. Another strange thing is that, in my real full-code, Nsight says I have uncoalesced read in the second read (where I increase the pointer by threadID and read that address) but does not mark the read from global to shared memory as uncoalesced. Unlike that, in the example code below, Nsight marks both of them as uncoalesced reads. Also, my full-code uses the __shfl_down_sync warpReduce method (called by blockReduceSum) but I'm doing atomic sums below to make the example code shorter.
#include <stdio.h>
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort = true) {
if (code != cudaSuccess) {
fprintf(stderr, "GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
if (abort) exit(code);
}
}
#define nbins 512
#define nsegs 340
#define ntemplates 100
__device__ float2 * d;
__global__ void kernel(float * d_all_sums, int * d_template_indices) {
float final = 0.0;
// blockIdx.x gives template index, threadIdx.x normally gets bin index but use it to mean segment index for loading global memory to shared.
__shared__ float2 * power_at_0_pointers[nsegs];
if (threadIdx.x < nsegs)
power_at_0_pointers[threadIdx.x] = &d[ __ldg(&d_template_indices[blockIdx.x * nsegs + threadIdx.x]) ]; // Uncoalesced here. Real to ideal ratio ~ 1.12.
__syncthreads();
__shared__ float power_sum;
for (int i = 0; i < nsegs; i++) {
__shared__ float powers[nbins];
float2 * pow_first_bin = power_at_0_pointers[i];
float2 input_power_c = *(pow_first_bin + threadIdx.x); // Uncoalesced here. Real to ideal ratio ~ 1.09.
float power = input_power_c.x * input_power_c.x + input_power_c.y * input_power_c.y;
power = (2 * power - 1.0) / 2.0;
powers[threadIdx.x] = power;
atomicAdd(&power_sum, powers[threadIdx.x]);
final += power / power_sum;
}
if (threadIdx.x == 0)
d_all_sums[blockIdx.x] = final;
}
int random(int min, int max){
return min + rand() / (RAND_MAX / (max - min + 1) + 1);
}
int main(){
float2 * h;
float * h_all_sums;
float2 * d_ptr;
float * d_all_sums;
int * h_template_indices, * d_template_indices;
h_template_indices = (int *) malloc(ntemplates * nsegs * sizeof(int));
h = (float2 *) malloc(nbins * nsegs * ntemplates * sizeof(float2));
h_all_sums = (float *) malloc(ntemplates * sizeof(float));
memset(h_all_sums, 0.0, ntemplates);
for (int k = 0; k < ntemplates; k++) {
for (int i = 0; i < nsegs; i++) {
h_template_indices[k * nsegs + i] = random(0, ntemplates * nsegs - nbins);
for (int j = 0; j < nbins; j++)
h[k * nbins * nsegs + i * nbins + j] = make_float2(100 * (float) rand() / (float)(RAND_MAX), 100 * (float) rand() / (float)(RAND_MAX));
}
}
gpuErrchk( cudaMalloc((void**) &d_ptr, nbins * nsegs * ntemplates * sizeof(float2)) );
gpuErrchk( cudaMemcpy(d_ptr, h, nbins * nsegs * ntemplates * sizeof(float2), cudaMemcpyHostToDevice) );
gpuErrchk( cudaMemcpyToSymbol(d, &d_ptr, sizeof(float2*)) );
gpuErrchk( cudaMalloc( (void**) &d_all_sums, ntemplates * sizeof(float) ) );
gpuErrchk( cudaMalloc((void**) &d_template_indices, nsegs * ntemplates * sizeof(int)) );
gpuErrchk( cudaMemcpy(d_template_indices, h_template_indices, nsegs * ntemplates * sizeof(int), cudaMemcpyHostToDevice) );
kernel<<<ntemplates, nbins>>>(d_all_sums, d_template_indices);
gpuErrchk( cudaMemcpy(h_all_sums, d_all_sums, ntemplates * sizeof(float), cudaMemcpyDeviceToHost) );
FILE *f = fopen("test_output.txt", "w");
if (f != NULL) {
for (int k = 0; k < ntemplates; k++)
fprintf(f, "k = %d; power = %f.\n", k, h_all_sums[k]);
}
fclose( f );
gpuErrchk( cudaFree(d_ptr) );
gpuErrchk( cudaFree(d_all_sums) );
gpuErrchk( cudaFree(d_template_indices) );
free( h );
free( h_all_sums );
free( h_template_indices );
gpuErrchk( cudaPeekAtLastError() );
gpuErrchk( cudaDeviceSynchronize() );
printf("All done.\n");
}
EDIT: I've included the result of Nsight Compute about coalescence below. I've also included code that matches my full code more closely, in terms of how block reduction is done (shuffle instead of atomics). With that change, now Nsight says I have uncoalesced access only at one stop (another one in a library but that's obviously out of my control). The difference in summing/reduction method, for some reason, seemed to have made the read from global to shared memory to coalesced?
#include <stdio.h>
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort = true) {
if (code != cudaSuccess) {
fprintf(stderr, "GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
if (abort) exit(code);
}
}
__inline__ __device__ float warpReduceSum(float val) {
for (int offset = warpSize/2; offset > 0; offset /= 2)
val += __shfl_down_sync(0xffffffff, val, offset);
return val;
}
__inline__ __device__ float blockReduceSum(float val) {
static __shared__ float shared[32];
int lane = threadIdx.x % warpSize;
int wid = threadIdx.x / warpSize;
val = warpReduceSum(val);
if (lane == 0) shared[wid] = val;
__syncthreads();
val = (threadIdx.x < blockDim.x / warpSize) ? shared[lane] : float(0.0);
if (wid == 0) val = warpReduceSum(val);
return val;
}
#define nbins 512
#define nsegs 340
#define ntemplates 100
__device__ float2 * d;
__global__ void kernel(float * d_all_sums, int * d_template_indices) {
float final = 0.0;
// blockIdx.x gives template index, threadIdx.x normally gets bin index but use it to mean segment index for loading global memory to shared.
__shared__ float2 * power_at_0_pointers[nsegs];
if (threadIdx.x < nsegs)
power_at_0_pointers[threadIdx.x] = &d[ __ldg(&d_template_indices[blockIdx.x * nsegs + threadIdx.x]) ];
__syncthreads();
for (int i = 0; i < nsegs; i++) {
__shared__ float powers[nbins];
float2 * pow_first_bin = power_at_0_pointers[i];
float2 input_power_c = *(pow_first_bin + threadIdx.x); // Uncoalesced here.
float power = input_power_c.x * input_power_c.x + input_power_c.y * input_power_c.y;
power = (2 * power - 1.0) / 2.0;
powers[threadIdx.x] = 1.0;
float power_sum = blockReduceSum(powers[threadIdx.x]);
final += power / power_sum;
}
if (threadIdx.x == 0)
d_all_sums[blockIdx.x] = final;
}
int random(int min, int max){
return min + rand() / (RAND_MAX / (max - min + 1) + 1);
}
int main(){
float2 * h;
float * h_all_sums;
float2 * d_ptr;
float * d_all_sums;
int * h_template_indices, * d_template_indices;
h_template_indices = (int *) malloc(ntemplates * nsegs * sizeof(int));
h = (float2 *) malloc(nbins * nsegs * ntemplates * sizeof(float2));
h_all_sums = (float *) malloc(ntemplates * sizeof(float));
memset(h_all_sums, 0.0, ntemplates);
for (int k = 0; k < ntemplates; k++) {
for (int i = 0; i < nsegs; i++) {
h_template_indices[k * nsegs + i] = random(0, ntemplates * nsegs - nbins);
for (int j = 0; j < nbins; j++)
h[k * nbins * nsegs + i * nbins + j] = make_float2(100 * (float) rand() / (float)(RAND_MAX), 100 * (float) rand() / (float)(RAND_MAX));
}
}
gpuErrchk( cudaMalloc((void**) &d_ptr, nbins * nsegs * ntemplates * sizeof(float2)) );
gpuErrchk( cudaMemcpy(d_ptr, h, nbins * nsegs * ntemplates * sizeof(float2), cudaMemcpyHostToDevice) );
gpuErrchk( cudaMemcpyToSymbol(d, &d_ptr, sizeof(float2*)) );
gpuErrchk( cudaMalloc( (void**) &d_all_sums, ntemplates * sizeof(float) ) );
gpuErrchk( cudaMalloc((void**) &d_template_indices, nsegs * ntemplates * sizeof(int)) );
gpuErrchk( cudaMemcpy(d_template_indices, h_template_indices, nsegs * ntemplates * sizeof(int), cudaMemcpyHostToDevice) );
kernel<<<ntemplates, nbins>>>(d_all_sums, d_template_indices);
gpuErrchk( cudaMemcpy(h_all_sums, d_all_sums, ntemplates * sizeof(float), cudaMemcpyDeviceToHost) );
FILE *f = fopen("test_output.txt", "w");
if (f != NULL) {
for (int k = 0; k < ntemplates; k++)
fprintf(f, "k = %d; power = %f.\n", k, h_all_sums[k]);
}
fclose( f );
gpuErrchk( cudaFree(d_ptr) );
gpuErrchk( cudaFree(d_all_sums) );
gpuErrchk( cudaFree(d_template_indices) );
free( h );
free( h_all_sums );
free( h_template_indices );
gpuErrchk( cudaPeekAtLastError() );
gpuErrchk( cudaDeviceSynchronize() );
printf("All done.\n");
}
EDIT2: Added memory chart and tables as requested.



