Как заставить gcc использовать все регистры SSE (или AVX)?


Я пытаюсь написать некоторый вычислительно интенсивный код для Windows x64 target, с SSE или новыми инструкциями AVX, компиляцией в GCC 4.5.2 и 4.6.1, MinGW64 (TDM GCC build и некоторые пользовательские сборки). Мои параметры компилятора - -O3 -mavx. (-m64 подразумевается)

Короче говоря, я хочу выполнить некоторые длительные вычисления на 4 3D векторах упакованных поплавков. Для этого требуется 4x3=12 регистров xmm или ymm для хранения и 2 или 3 регистра для временных результатов. Это должно ИМХО плотно вписаться в 16 доступные регистры SSE (или AVX) доступны для 64-битных целей. Однако GCC производит очень неоптимальный код с разливом регистров, используя только регистры xmm0-xmm10 и перетасовывая данные из и в стек. Мой вопрос:

Есть ли способ убедить GCC использовать все регистры xmm0-xmm15?

Чтобы исправить идеи, рассмотрим следующий код SSE (только для иллюстрации):

void example(vect<__m128> q1, vect<__m128> q2, vect<__m128>& a1, vect<__m128>& a2) {
    for (int i=0; i < 10; i++) {
        vect<__m128> v = q2 - q1;
        a1 += v;
//      a2 -= v;

        q2 *= _mm_set1_ps(2.);
    }
}

Здесь vect<__m128> - это просто struct из 3 __m128, с естественным сложением и умножением на скалярный. Когда строка a2 -= v закомментирована, то есть нам нужны только регистры 3x3 для хранения, так как мы игнорируем a2, полученный код действительно прост без ходов, все выполняется в регистрах xmm0-xmm10. Когда я удаляю комментарий a2 -= v, код довольно ужасен с большим количеством перетасовки между регистрами и стеком. Даже если компилятор может просто использовать регистры xmm11-xmm13 или что-то в этом роде.

Я на самом деле не видел, чтобы GCC использовал какой-либо из регистров xmm11-xmm15 где-либо во всех моих код пока нет. Что я делаю не так? Я понимаю, что это регистры, сохраненные вызываемым пользователем, но эти накладные расходы полностью оправданы упрощением кода цикла.

2 9

2 ответа:

Два пункта:

    Во-первых, вы делаете много предположений. Разлив регистров довольно дешев на процессорах x86 (из-за быстрого кэширования L1 и затенения регистров и других трюков), а 64-разрядные регистры являются более дорогими для доступа (с точки зрения больших инструкций), поэтому может оказаться, что версия GCC так же быстра или быстрее, чем та, которую вы хотите. Во-вторых, GCC, как и любой компилятор, делает наилучшее распределение регистров, которое он может. Нет никакого "пожалуйста, сделай лучше параметр "Регистрация распределения", потому что если бы он был, он всегда был бы включен. Компилятор не пытается досадить вам. (Распределение регистров-это NP-полная проблема, насколько я помню, поэтому компилятор никогда не сможет создать идеальное решение. Лучшее, что он может сделать, - это приблизиться)

Итак, если вы хотите улучшить распределение регистров, у вас есть два варианта:

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

На самом деле, то, что вы видите, не разливы, это gcc, работающий на a1 и a2 в памяти, потому что он не может знать, являются ли они псевдонимами. Если вы объявите последние два параметра как vect<__m128>& __restrict__, GCC может и будет регистрировать выделение a1 и a2.