Явное указание G CC 9.2 отключить l oop, чтобы разрешить автоматическую векторизацию - PullRequest
0 голосов
/ 11 апреля 2020

Я работаю над проектом, который требует автоматической c векторизации больших циклов. Для компиляции обязательно использовать G CC. Минимальный случай проблемы может быть следующим:

#define VLEN 4
#define NTHREADS 4
#define AVX512_ALIGNMENT 64

#define NUM_INTERNAL_ITERS 5

#define real double

typedef struct private_data {
    /*
     * Alloc enough space for private data and MEM_BLOCK_SIZE bytes of padding.
     * Private data must be allocated all at once to squeeze cache performance by only
     * padding once per CPU.
     */
    real *contiguous_data;

    /*
     * Pointers to corresponding index in contiguous_data.
     */
    real *array_1;
    real *array_2;
} private_data_t;

private_data_t private_data[NTHREADS];

int num_iter;

void minimum_case(const int thread) {

    // Reference to thread private data.

    real *restrict array_1 =
        __builtin_assume_aligned(private_data[thread].array_1, AVX512_ALIGNMENT);

    real *restrict array_2 =
        __builtin_assume_aligned(private_data[thread].array_2, AVX512_ALIGNMENT);

    for (int i = 0; i < num_iter; i++) {
        for (int k = 0; k < NUM_INTERNAL_ITERS; ++k) {

            int array_1_entry =
                (k * (NUM_INTERNAL_ITERS) * VLEN) +
                i * NUM_INTERNAL_ITERS * NUM_INTERNAL_ITERS * VLEN;

            int array_2_entry =
                (k * (NUM_INTERNAL_ITERS) * VLEN) +
                i * NUM_INTERNAL_ITERS * VLEN;

#pragma GCC unroll 1
#pragma GCC ivdep
            for (int j = 0; j < VLEN; j++) {

                real pivot;

                int a_idx = array_1_entry + VLEN * 0 + j;
                int b_idx = array_1_entry + VLEN * 1 + j;
                int c_idx = array_1_entry + VLEN * 2 + j;
                int d_idx = array_1_entry + VLEN * 3 + j;

                int S_idx = array_2_entry + VLEN * 0 + j;

                if (k == 0) {

                    pivot = array_1[a_idx];

                    // b = b / a
                    array_1[b_idx] /= pivot;

                    // c = c / a
                    array_1[c_idx] /= pivot;

                    // d = d / a
                    array_1[d_idx] /= pivot;

                    // S = S / a
                    array_2[S_idx] /= pivot;
                }

                int e_idx = array_1_entry + VLEN * 4 + j;
                int f_idx = array_1_entry + VLEN * 5 + j;
                int g_idx = array_1_entry + VLEN * 6 + j;
                int k_idx = array_1_entry + VLEN * 7 + j;

                int T_idx = array_2_entry + VLEN * 1 + j;

                pivot = array_1[e_idx];

                // f = f - (e * b)
                array_1[f_idx] -= array_1[b_idx]
                                  * pivot;

                // g = g - (e * c)
                array_1[g_idx] -= array_1[c_idx]
                                  * pivot;

                // k = k - (e * d)
                array_1[k_idx] -= array_1[d_idx]
                                  * pivot;

                // T = T - (e * S)
                array_2[T_idx] -= array_2[S_idx]
                                  * pivot;
            }
        }
    }
}

Для этого конкретного случая c G CC использует 16B-векторы вместо 32B-векторов для автоматической c векторизации. Довольно легко увидеть, что поток управления зависит от условия, которое может быть проверено из внутреннего l oop, но G CC не выполняет никакого l oop -переключения.

Отключение l oop может быть сделано вручную, но, пожалуйста, обратите внимание, что это минимальный случай проблемы, реальное l oop имеет сотни строк, и выполнение ручного переключения l oop приведет к большому количеству кода избыточность. Я пытаюсь найти способ заставить G CC создавать разные циклы для разных условий, которые можно проверить из внутреннего l oop.

В настоящее время я использую G CC 9.2 с следующие флаги: -Ofast -march=native -std=c11 -fopenmp -ftree-vectorize -ffast-math -mavx -mno-avx256-split-unaligned-load -mno-avx256-split-unaligned-store -fopt-info-vec-optimized

1 Ответ

0 голосов
/ 12 апреля 2020

Особенно, если в реальном l oop есть сотни строк, я настоятельно рекомендую выделить это в отдельную функцию, что сделает переключение вручную (при необходимости) не таким уж плохим.

Следующее должно быть эквивалентным вашему коду (обратите внимание, я также учел некоторые вычисления индекса - это на самом деле можно упростить еще больше):

inline void inner_loop(real *restrict array_1, real *restrict array_2,
                       int const first) {
#pragma GCC unroll 1
  for (int j = 0; j < VLEN; j++) {
    int a_idx = VLEN * 0 + j;
    int b_idx = VLEN * 1 + j;
    int c_idx = VLEN * 2 + j;
    int d_idx = VLEN * 3 + j;

    int S_idx = VLEN * 0 + j;

    if (first) {
      real pivot = array_1[a_idx];

      array_1[b_idx] /= pivot;      // b = b / a
      array_1[c_idx] /= pivot;      // c = c / a
      array_1[d_idx] /= pivot;      // d = d / a
      array_2[S_idx] /= pivot;      // S = S / a
    }

    int e_idx = VLEN * 4 + j;
    int f_idx = VLEN * 5 + j;
    int g_idx = VLEN * 6 + j;
    int k_idx = VLEN * 7 + j;

    int T_idx = VLEN * 1 + j;

    real pivot = array_1[e_idx];

    array_1[f_idx] -= array_1[b_idx] * pivot;    // f = f - (e * b)
    array_1[g_idx] -= array_1[c_idx] * pivot;    // g = g - (e * c)
    array_1[k_idx] -= array_1[d_idx] * pivot;    // k = k - (e * d)
    array_2[T_idx] -= array_2[S_idx] * pivot;    // T = T - (e * S)
  }
}

void minimum_case(const int thread) {
  // Reference to thread private data.

  real *restrict array_1 =
      __builtin_assume_aligned(private_data[thread].array_1, AVX512_ALIGNMENT);

  real *restrict array_2 =
      __builtin_assume_aligned(private_data[thread].array_2, AVX512_ALIGNMENT);

  for (int i = 0; i < num_iter; i++) {
    real *array_1_i =
        array_1 + i * NUM_INTERNAL_ITERS * NUM_INTERNAL_ITERS * VLEN;
    real *array_2_i = array_2 + i * NUM_INTERNAL_ITERS * VLEN;

    inner_loop(array_1_i, array_2_i, 0);
    for (int k = 1; k < NUM_INTERNAL_ITERS; ++k) {
      int array_1_entry = (k * (NUM_INTERNAL_ITERS)*VLEN);

      int array_2_entry = (k * (NUM_INTERNAL_ITERS)*VLEN);
      inner_loop(array_1_i + array_1_entry, array_2_i + array_2_entry, 1);
    }
  }
}

Полная демонстрация на Godbolt: https://godbolt.org/z/wMgSnr

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