Как векторизовать мой цикл с помощью g++?

Вводные ссылки, которые я нашел при поиске:

Как вы можете видеть, большинство из них для C, но я думал, что они могут работать и на С++. Вот мой код:

template<typename T>
//__attribute__((optimize("unroll-loops")))
//__attribute__ ((pure))
void foo(std::vector<T> &p1, size_t start,
            size_t end, const std::vector<T> &p2) {
  typename std::vector<T>::const_iterator it2 = p2.begin();
  //#pragma simd
  //#pragma omp parallel for
  //#pragma GCC ivdep Unroll Vector
  for (size_t i = start; i < end; ++i, ++it2) {
    p1[i] = p1[i] - *it2;
    p1[i] += 1;
  }
}

int main()
{
    size_t n;
    double x,y;
    n = 12800000;
    vector<double> v,u;
    for(size_t i=0; i<n; ++i) {
        x = i;
        y = i - 1;
        v.push_back(x);
        u.push_back(y);
    }
    using namespace std::chrono;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    foo(v,0,n,u);
    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    duration<double> time_span = duration_cast<duration<double>>(t2 - t1);

    std::cout << "It took me " << time_span.count() << " seconds.";
    std::cout << std::endl;
    return 0;
}

Я использовал все подсказки, которые можно увидеть в комментариях выше, но я не получил ускорения, как показывает примерный вывод (при первом запуске с раскомментированным значением #pragma GCC ivdep Unroll Vector:

[email protected]:~/Downloads$ g++ test.cpp -O3 -std=c++0x -funroll-loops -ftree-vectorize -o test
[email protected]:~/Downloads$ ./test
It took me 0.026575 seconds.
[email protected]:~/Downloads$ g++ test.cpp -O3 -std=c++0x -o test
[email protected]:~/Downloads$ ./test
It took me 0.0252697 seconds.

Есть ли надежда? Или флаг оптимизации O3 просто делает трюк? Любые предложения по ускорению этого кода (функция foo) приветствуются!

Моя версия g++:

[email protected]:~/Downloads$ g++ --version
g++ (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1

Обратите внимание, что тело цикла является случайным. Мне не интересно переписывать его в какой-то другой форме.


ИЗМЕНИТЬ

Ответ, говорящий, что больше ничего не может быть сделано, также приемлемо!

Ответ 1

Флаг O3 автоматически включает-векторизовать. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

-O3 включает все оптимизации, заданные -O2, а также включает -finline-functions, -funswitch-loops, -fpredictive-commoning, -fgcse-after-reload, -ftree-loop-vectorize, -ftree- loop-distribute-patterns, -ftree-slp-vectorize, -fvect-cost-model, -ftree-partial-pre и -fipa-cp-clone options

Итак, в обоих случаях компилятор пытается сделать векторизацию цикла.

Использование g++ 4.8.2 для компиляции с помощью:

g++ test.cpp -O2 -std=c++0x -funroll-loops -ftree-vectorize -ftree-vectorizer-verbose=1 -o test

Дает следующее:

Analyzing loop at test.cpp:16                                                                                                                                                                                                                                               


Vectorizing loop at test.cpp:16                                                                                                                                                                                                                                             

test.cpp:16: note: create runtime check for data references *it2$_M_current_106 and *_39                                                                                                                                                                                    
test.cpp:16: note: created 1 versioning for alias checks.                                                                                                                                                                                                                   

test.cpp:16: note: LOOP VECTORIZED.                                                                                                                                                                                                                                         
Analyzing loop at test_old.cpp:29                                                                                                                                                                                                                                               

test.cpp:22: note: vectorized 1 loops in function.                                                                                                                                                                                                                          

test.cpp:18: note: Unroll loop 7 times                                                                                                                                                                                                                                      

test.cpp:16: note: Unroll loop 7 times                                                                                                                                                                                                                                      

test.cpp:28: note: Unroll loop 1 times  

Компиляция без флага -ftree-vectorize:

g++ test.cpp -O2 -std=c++0x -funroll-loops -ftree-vectorizer-verbose=1 -o test

Возвращает только это:

test_old.cpp:16: note: Unroll loop 7 times

test_old.cpp:28: note: Unroll loop 1 times

Строка 16 - это начало функции цикла, поэтому компилятор определенно векторизует ее. Проверка ассемблера подтверждает это тоже.

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

Но вот еще несколько вещей, которые вы можете попробовать:

  • Используйте квалификатор __restrict__, чтобы сообщить компилятору, что между массивами нет перекрытия.

  • Сообщите компилятору, что массивы выровнены с __builtin_assume_aligned (не переносимым)

Здесь мой результирующий код (я удалил шаблон, так как вы захотите использовать другое выравнивание для разных типов данных)

#include <iostream>
#include <chrono>
#include <vector>

void foo( double * __restrict__ p1,
          double * __restrict__ p2,
          size_t start,
          size_t end )
{
  double* pA1 = static_cast<double*>(__builtin_assume_aligned(p1, 16));
  double* pA2 = static_cast<double*>(__builtin_assume_aligned(p2, 16));

  for (size_t i = start; i < end; ++i)
  {
      pA1[i] = pA1[i] - pA2[i];
      pA1[i] += 1;
  }
}

int main()
{
    size_t n;
    double x, y;
    n = 12800000;
    std::vector<double> v,u;

    for(size_t i=0; i<n; ++i) {
        x = i;
        y = i - 1;
        v.push_back(x);
        u.push_back(y);
    }

    using namespace std::chrono;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    foo(&v[0], &u[0], 0, n );
    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    duration<double> time_span = duration_cast<duration<double>>(t2 - t1);

    std::cout << "It took me " << time_span.count() << " seconds.";
    std::cout << std::endl;

    return 0;
}

Как я уже сказал, у меня были проблемы с согласованными измерениями времени, поэтому не могу подтвердить, что это даст вам увеличение производительности (или даже даже уменьшение!)

Ответ 2

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

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