I have the following code as part of a reorganization of data for later use in a CUDA kernel:
thrust::device_ptr<int> dev_ptr = thrust::device_pointer_cast(dev_particle_cell_indices);
int total = 0;
for(int i = 0; i < num_cells; i++) {
particle_offsets[i] = total;
// int num = 0;
int num = thrust::count(dev_ptr, dev_ptr + num_particles, i);
particle_counts[i] = num;
total += num;
}
Now, if I set num
to 0 (uncomment the 5th line, and comment out the 6th), the application runs at over 30 fps, which is my goal. However, when I set num
equal to the thrust::count
call, the framerate drops to about 1-2 fps. Why would that happen?
My understanding is that thrust is supposed to be a collection of highly optimized algorithms which harness the power of the GPU, so I'm surprised that it would have that kind of impact on the performance of my program. This is my first time using thrust though, so I may be unaware of some important details.
Is there something about using thrust::count
in a loop which is causing it to run so slowly? How can I optimize my usage of it?
To give some figures, in my current test case, num_particles
is about 2000, and num_cells
is about 1500.
The performance of thrust::count
is just fine, it is the way you are trying to use it that is problematic for performance. If you had a lot of particles and only a few cells, then your implementation using thrust::count
is probably not a bad idea. Your problem is that you have 1500 cells. That means 1500 invocations of count
and 1500 device to host memory transfers each time you want to do the computation. The latency of all the kernel launches and all the PCI-e bus copies will kill performance, as you have found.
A better approach for a large number of cells would be something like this:
thrust::device_ptr<int> rawin = thrust::device_pointer_cast(dev_particle_cell_indices);
// Sort a scratch copy of the cell indices by value
thrust::device_vector<int> cidx(num_particles);
thrust::copy(rawin, rawin+num_particles, cidx.begin());
thrust::sort(cidx.begin(), cidx.end());
// Use binary search to extract all the cell counts/offsets
thrust::counting_iterator<int> cellnumber(0);
thrust::device_vector<int> offsets(num_cells), counts(num_cells);
// Offsets come from lower_bound of the ordered cell numbers
thrust::lower_bound(cidx.begin(), cidx.end(), cellnumber, cellnumber+num_cells, offsets.begin());
// Counts come from the adjacent_difference of the upper_bound of the ordered cell numbers
thrust::upper_bound(cidx.begin(), cidx.end(), cellnumber, cellnumber+num_cells, counts.begin());
thrust::adjacent_difference(counts.begin(), counts.end(), counts.begin());
// Copy back to the host pointer
thrust::copy(counts.begin(), counts.end(), particle_counts);
thrust::copy(offsets.begin(), offsets.end(), particle_offsets);
Here, we first sort a local copy of the cell indices, then use the thrust binary search functions to perform the same operation as your code, but with far fewer passes through the data in GPU memory and only two device to host memory copies to get all the results back to the host.
When I benchmark your thrust::count
implementation with the code I posted above for a non-trivial case (10000 random particles and 2000 cells on a GeForce 320M with CUDA 4.1 on OS X), I find that your version takes about 0.95 seconds to run, whereas the sort/search version takes about 0.003 seconds to run. So there is probably several hundred times speedup available to you using thrust if you use a more efficient strategy and more appropriate algorithms.