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

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

Короче говоря, я хочу выполнить длительные вычисления для 4 трехмерных векторов упакованных поплавков. Для этого требуется 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 во всем моем коде. Что я делаю неправильно? Я понимаю, что это регистры, сохраняемые вызываемым пользователем, но эти накладные расходы полностью оправдываются упрощением кода цикла.


person Norbert P.    schedule 11.05.2011    source источник


Ответы (2)


Два момента:

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

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

  • написать лучший распределитель регистров и пропатчить его в GCC, или
  • обойти GCC и переписать функцию на ассемблере, чтобы вы могли точно контролировать, какие регистры и когда используются.
person jalf    schedule 11.05.2011
comment
На самом деле, есть еще один вариант, о котором я должен был упомянуть: поэкспериментировать с вашим кодом, чтобы сделать его более понятным для GCC и его регистрового распределителя. Объявление новых переменных вместо повторного использования старых может помочь, сводя к минимуму область действия и время жизни каждой переменной, а просто экспериментирование взад и вперед может помочь вам уговорить GCC создавать другой код. - person jalf; 11.05.2011
comment
Спасибо за быстрый ответ. Вы правы, я слишком много предполагал. Получается, что компилятор выдает нужный код, если я сохраняю a1 и a2 во временных локальных переменных на время вычислений. По какой-то причине это не было проблемой для компилятора, когда a2 -= v был закомментирован. Не уверен, почему. Что касается длины инструкции, новая кодировка VEX делает доступ ко всем 16 регистрам эквивалентным. - person Norbert P.; 11.05.2011
comment
Оптимальное распределение регистров (без сброса/заполнения) является линейным O(N) для IR в форме SSA с использованием либо SEO, либо PEO. Я не могу вспомнить, какой текущий лучший результат для распределения с разливом/наполнением. - person thechao; 28.03.2012
comment
Ну да, когда я говорю о распределении регистров, я имею в виду проблему в целом, а не только несколько частных случаев, с которыми легко справиться. - person jalf; 28.03.2012
comment
@jalf Дальнейшее чтение показывает, что конечная фаза разлива полиномиальна, хотя и что-то вроде O (V ^ 3). Я не уверен, что вы подразумеваете под «несколько особых случаев», все программы могут быть представлены в SSA-форме, и все программы SSA-формы могут быть раскрашены k в линейном времени с использованием SEO или PEO. - person thechao; 30.03.2012
comment
@thechao: я имею в виду именно то, что сказал. В общем случае оптимальное (как и идеальное) размещение регистров является NP-полной задачей. Есть много случаев, когда это проще, но в целом было показано, что он NP-полный. Это означает, что если вы можете сделать это за линейное время, то вы решили целую группу неприятных проблем, которые не смогли решить самые умные головы на планете. Что довольно приятно. (Я не проверял, поступит ли приз тысячелетия, но если да, поторопитесь и поговорите с ними. У них есть несколько миллионов долларов, которые ждут парня, который решит эту проблему. ;) - person jalf; 30.03.2012
comment
@jalf Похоже, вы не знакомы с современным дизайном компилятора. Вот следующие статьи и презентации по оптимальному распределению регистров O(N) для общего случая: cs.cmu.edu/afs/cs/academic/class/15745-s07/www/papers/ docstoc.com/docs/11350331/ Я понимаю, что раскраска графа может быть сводится к распределению регистров, когда RA ставится как общая задача графа. Однако программы не являются общими графами и поэтому оптимально раскрашиваются за O(N). Это достаточно новый результат. - person thechao; 12.04.2012
comment
@thechao Это не вывод из этого исследования. Вы можете преобразовать любую программу в SSA и выполнить оптимальное распределение регистров в SSA за линейное время, но это не означает, что вы сделали оптимальное распределение регистров для исходной программы. SSA вводит больше переменных, чтобы упростить структуру графа. С этой более простой структурой вы можете оптимально распределить регистры, но это оптимально только для этой более простой структуры с большим количеством переменных; конечный результат не является оптимальным распределением для исходной проблемы. Прочитайте презентации, на которые вы ссылались; говорят именно так! - person Brian Campbell; 17.11.2012
comment
Спасибо вам обоим. @thechao, ты прав, я не знал об этих бумагах. Спасибо, что обратили на них мое внимание. Очень интересные вещи. Но, как говорит Брайан, есть еще несколько предостережений. Тем не менее, я исправляюсь. :) - person jalf; 17.11.2012

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

person user511824    schedule 18.07.2014