Чтобы добавить к существующему ответу (который является полностью правильным), рассмотрите следующую тривиально полную версию кода, который вы разместили в своем вопросе:
__global__
void Mykernel(float* data, int size)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
for(; tid < size; tid+= blockDim.x * gridDim.x) data[tid] = 54321.f;
}
int main()
{
int rows = 2048;
int cols = 2048;
int blockSize = 32;
dim3 block(blockSize);
int nStreams = 4; // preparation for streams
cudaStream_t *streams = (cudaStream_t *)malloc(nStreams * sizeof(cudaStream_t));
for(int ii=0;ii<nStreams;ii++){
cudaStreamCreate(&streams[ii]);
}
float* d_Data;
cudaMalloc(&d_Data, sizeof(float) * rows * cols);
int streamSize = rows * cols / nStreams;
dim3 streamGrid = dim3(4);
for(int jj=0;jj<nStreams;jj++){
int offset = jj * streamSize;
Mykernel<<<streamGrid,block,0,streams[jj]>>>(&d_Data[offset],streamSize);
} // d_Data is the matrix on gpu
cudaDeviceSynchronize();
cudaDeviceReset();
}
Обратите внимание на два отличия - количество блоковколичество запускаемых для каждого ядра уменьшается, а общее количество вычислений для каждого потока увеличивается путем установки rows
в 2048. Само ядро содержит цикл с шагом сетки, который позволяет каждому потоку обрабатывать несколько входных данных, обеспечивая обработку всего входного набора данных.независимо от того, сколько всего блоков / потоков запущено.
Профилирование на аналогичном графическом процессоре Maxwell для вашего устройства показывает это:
то есть ядра перекрываются.Теперь давайте уменьшим размер задачи до размера, указанного в вашем вопросе (строки = 5):
Ядра больше не перекрываются.Зачем?Поскольку задержка драйвера и устройства достаточно высока, а время выполнения каждого ядра достаточно короткое, чтобы не было времени для перекрытия выполнения, даже если ресурсы устройства в противном случае позволили бы это.Таким образом, помимо ограничений требований к ресурсам, описанных в другом ответе, объем вычислений должен быть достаточно большим, чтобы компенсировать фиксированную задержку, связанную с планированием запуска ядра в потоке.
Наконец, я хотел бы предложить правильный подход кустановка схемы параллельного выполнения на основе потоков должна выглядеть примерно так:
int blockSize = 32;
dim3 block(blockSize);
int blocksperSM, SMperGPU = 13; // GPU specific
cudaOccupancyMaxActiveBlocksPerMultiprocessor(&blocksperSM, Mykernel, blockSize, 0); // kernel specific
dim3 streamGrid = blocksperSM * (SMperGPU / nStreams); // assume SMperGPU >> nstreams
Здесь идея состоит в том, что количество доступных SM (приблизительно) поровну поделено между потоками и количеством блоков, которыеМаксимально занимаемая каждым SM для выбранного размера блока получается для ядра через оккупационный API.
Этот профиль выглядит следующим образом:
, что дает как перекрытие, так и короткое время выполнения, правильно сопоставляя требования к ресурсам ядрадо емкости графического процессора для случая с rows = 2048
.