Есть ли польза от этой комбинации указателей и циклов? - PullRequest
1 голос
/ 06 ноября 2019

Я работаю в CUDA C Programming от Cheng, и наткнулся на этот кусок кода:

void sumMatrixOnHost (float *A, float *B, float *C, const int nx, const int ny) {
    float *ia = A;
    float *ib = B;
    float *ic = C;
    for (int iy=0; iy<ny; iy++) {
        for (int ix=0; ix<nx; ix++) {
            ic[ix] = ia[ix] + ib[ix];
        }
        ia += nx; ib += nx; ic += nx;
    }
}

Это для добавления матрицы, при котором матрицы хранятся в формате строки-майора.

Как я понимаю, внутренний цикл for выполняет итерацию по строке и выполняет добавление элемента, а внешний цикл for затем используется для приращения указателей к началу следующей строки.

Почемуэтот подход лучше, чем использование указателей по всей матрице, т.е.

for (int i=0; i<ny*nx; i++) {
    ic[i] = ia[i] + ib[i];
}

или двойных для циклов, т.е.

for (int iy=0; iy<ny; iy++) {
    for (int ix=0; ix<nx; ix++) {
        ic[iy*nx+ix] = ia[iy*nx+ix] + ib[iy*nx+ix];
    }
}

Это как-то связано с тем, как он оптимизируется компилятором?

Ответы [ 2 ]

3 голосов
/ 06 ноября 2019

Самый простой подход, всегда лучший:

for (int i=0; i<ny*nx; i++) {
    C[i] = A[i] + B[i];
}

Это будет быстрее, чем первое решение. Проблема разделения матрицы по строкам состоит в том, что векторизатор будет выполнять:

  • строку процесса в пакетах по 32 байта (размер YMM)
  • Обработать оставшуюся горстку значений вконец строки.
  • Теперь повторите для каждой строки!

Если вы сделаете это с одним циклом, сгенерированный код будет:

  • обрабатывать все данные в пакетах по 32 байта (размер YMM)
  • Обрабатывать оставшиеся несколько значений в конце матрицы, которые не выровнены по 32-байтовым блокам.

Первая версия просто добавляет бессмысленный код для обработки внутреннего цикла. Ни один из этого кода не нужен, он просто нарушает возможность векторизации всей матрицы.

2 голосов
/ 06 ноября 2019

Подход к sumMatrixOnHost лучше для оптимизации, и он должен выполняться быстрее (как правило), чем два предложенных вами подхода.

Для умножения alu требуется больше времени, чем для сложения. Таким образом, в sumMatrixOnHost нет умножения, в

for (int i=0; i<ny*nx; i++) {
    ic[i] = ia[i] + ib[i];
 }

умножение происходит в каждой итерации цикла. в

for (int iy=0; iy<ny; iy++) {
    for (int ix=0; ix<nx; ix++) {
        ic[iy*nx+ix] = ia[iy*nx+ix] + ib[iy*nx+ix];
    }
}

есть 3 умножения в каждой итерации цикла.

Более простым подходом может быть

 int n = ny*nx;
 for (int i=0; i<n; i++) {
    ic[i] = ia[i] + ib[i];
 }

, но при последнем подходе мы теряем еще одну вещь, которая хороша в sumMatrixOnHost, и это возможность выполнять операции над матричными блоками, а невся матрица.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...