Должен ли я объявить двойной массив с номером блока графического процессора во внутреннем или внешнем измерении?

Должен ли я объявить двойной массив с номером блока графического процессора во внутреннем или внешнем измерении?

например, должен ли я сделать

int payload[LEN][BLOCKS];

или

int payload[BLOCKS][LEN];

где LEN — очень большое число.

Я планирую, чтобы каждый блок проходил по двойному массиву, сохраняя постоянную размерность блока и повторяя размерность LEN.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
29
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Предполагая, что вы собираетесь получать доступ к данным блочно-ориентированным способом, вы хотите сделать последнее. Предположительно, это связано с тем, что когда вы загружаете первый элемент измерения "len", вы уже заплатили за отсутствие в кеше последующих семи элементов. В первом варианте возможно совместное использование строк кэша между блоками графического процессора, но совместное использование относительно ограничено и не на таком низком уровне.

Действительно, приведенный ниже код сообщает, что для выполнения второго варианта требуется 0,481 секунды, а для первого варианта — 0,979 секунды. Расположение данных с помощью блока во внешнем измерении примерно в два раза эффективнее.

#include <cuda_runtime_api.h>
#include <cuda.h>

#include <string>
#include <chrono>
#include <iostream>

#define BLOCKS 80
#define LEN (1 << 20)

void CheckCudaErrorAux (const char *file, unsigned line, const char *statement, cudaError_t err) {
    if (err == cudaSuccess)
        return;
    std::cerr << statement<<" returned " << cudaGetErrorString(err) << "("<<err<< ") at "<<file<<":"<<line << std::endl;
    exit (1);
}
#define CUDA_CHECK_RETURN(value) CheckCudaErrorAux(__FILE__,__LINE__, #value, value)

struct Data1 {
    int payload[LEN][BLOCKS];
};

struct Data2 {
    int payload[BLOCKS][LEN];
};


__global__ void f1(Data1 * data1) {
    int sum = 0;
    for (int i = 0; i < LEN; ++i) {
        sum += data1->payload[i][blockIdx.x];
    }
    printf("block %i has f1 sum %i\n", blockIdx.x, sum);
}

__global__ void f2(Data2 * data2) {
    int sum = 0;
    for (int i = 0; i < LEN; ++i) {
        sum += data2->payload[blockIdx.x][i];
    }
    printf("block %i has f2 sum %i\n", blockIdx.x, sum);
}


int main() {

    Data1 * data1 = (Data1 *) malloc(sizeof(Data1));
    Data2 * data2 = (Data2 *) malloc(sizeof(Data2));;

    for (int i = 0; i < LEN; ++i) {
        for (int b = 0; b < BLOCKS; ++b) {
            data1->payload[i][b] = i * b;
            data2->payload[b][i] = i * b;
        }
    }

    Data1 * data1_on_gpu;
    CUDA_CHECK_RETURN(cudaMalloc(&data1_on_gpu, sizeof(Data1)));
    Data2 * data2_on_gpu;
    cudaMalloc(&data2_on_gpu, sizeof(Data2));
    CUDA_CHECK_RETURN(cudaDeviceSynchronize());
    cudaMemcpy(data1_on_gpu, data1, sizeof(Data1), cudaMemcpyHostToDevice);
    cudaMemcpy(data2_on_gpu, data2, sizeof(Data1), cudaMemcpyHostToDevice);
    CUDA_CHECK_RETURN(cudaDeviceSynchronize());


    std::chrono::time_point<std::chrono::system_clock> t1 = std::chrono::system_clock::now();

    f1<<<80,1>>>(data1_on_gpu);
    CUDA_CHECK_RETURN(cudaDeviceSynchronize());
    std::chrono::time_point<std::chrono::system_clock> t2 = std::chrono::system_clock::now();

    f2<<<80,1>>>(data2_on_gpu);
    CUDA_CHECK_RETURN(cudaDeviceSynchronize());
    std::chrono::time_point<std::chrono::system_clock> t3 = std::chrono::system_clock::now();


    std::chrono::duration<double> duration_1_to_2 = t2 - t1;
    std::chrono::duration<double> duration_2_to_3 = t3 - t2;
    duration_1_to_2.count();

    printf("timer for 1st took %.3lf\n", duration_1_to_2.count());
    printf("timer for 2nd took %.3lf\n", duration_2_to_3.count());

}

Конечно. Но лучше всего использовать 1D-массив вместо 2D. Двумерный массив фрагментирован, каждый из указателей будет непрерывным блоком памяти, но весь массив не будет непрерывным. Если вы используете 1D-массивы, возможно, вы можете еще больше ускорить его, используя постоянную или текстурную память (в зависимости от вашего приложения).

Ander Biguri 29.05.2019 10:59

ни один из них не является эффективным шаблоном доступа, и вы никогда не захотите запускать блоки, состоящие из одного потока на блок, если вас интересует производительность на GPU. Кроме того, неясно, что вы понимаете, что такое параметры конфигурации запуска ядра. Если вы не верите, что вашему ядру f2 требуется ровно 1 байт динамически выделяемой разделяемой памяти (это не так), конфигурация <<<80,1,1>>> не имеет особого смысла. В любом случае, <<<80,1>>> или <<<80,1,1>>> плохо влияют на производительность.

Robert Crovella 29.05.2019 14:55

<<<80,1,1>>> была опечаткой — исправлено, спасибо. Полуочевидно, что мое реальное приложение имеет более одного потока на блок. Эти вопросы и ответы должны были помочь мне понять, как разместить одну из моих структур данных. В реальном приложении все потоки в блоке считываются из одной и той же записи в data[1/2]_on_gpu, поэтому я предполагаю, что дополнительные потоки не будут иметь значения для ответа на этот вопрос.

interestedparty333 29.05.2019 21:40

В каком-то смысле я рад, что разница в производительности составляет Только в 2 раза. Для меня это означает, что аппаратное обеспечение прошло долгий путь, компенсируя «плохое» программирование. Если бы разница была значительно больше, то «плохой» выбор здесь мог бы повлиять на производительность моей программы. Как бы то ни было, это, вероятно, не имеет большого значения.

interestedparty333 29.05.2019 21:47

Другие вопросы по теме