Как запретить GCC нарушить мои внутренние функции NEON?

Мне нужно написать оптимизированный код NEON для проекта, и я совершенно счастлив писать на ассемблере, но для переносимости / ремонтопригодности я использую инстринсики NEON. Этот код должен быть как можно более быстрым, поэтому я использую свой опыт в оптимизации ARM, чтобы правильно чередовать инструкции и избегать остановок конвейера. Независимо от того, что я делаю, GCC работает против меня и создает более медленный код, полный киосков.

Кто-нибудь знает, как заставить GCC уйти с дороги и просто перевести мои встроенные функции в код?

Вот пример: у меня есть простой цикл, который инвертирует и копирует значения с плавающей запятой. Он работает с 4 наборами по 4 за раз, чтобы дать некоторое время для загрузки памяти и выполнения инструкций. Осталось много регистров, так что нет причин так сильно портить вещи.

float32x4_t f32_0, f32_1, f32_2, f32_3;
int x;
for (x=0; x<n-15; x+=16)
{
   f32_0 = vld1q_f32(&s[x]);
   f32_1 = vld1q_f32(&s[x+4]);
   f32_2 = vld1q_f32(&s[x+8]);
   f32_3 = vld1q_f32(&s[x+12]);
   __builtin_prefetch(&s[x+64]);
   f32_0 = vnegq_f32(f32_0);
   f32_1 = vnegq_f32(f32_1);
   f32_2 = vnegq_f32(f32_2);
   f32_3 = vnegq_f32(f32_3);
   vst1q_f32(&d[x], f32_0);
   vst1q_f32(&d[x+4], f32_1);
   vst1q_f32(&d[x+8], f32_2);
   vst1q_f32(&d[x+12], f32_3);
} 

Это код, который он генерирует:

vld1.32 {d18-d19}, [r5]
vneg.f32  q9,q9        <-- GCC intentionally causes stalls
add r7,r7,#16
vld1.32 {d22-d23}, [r8]
add r5,r1,r4
vneg.f32 q11,q11   <-- all of my interleaving is undone (why?!!?)
add r8,r3,#256
vld1.32 {d20-d21}, [r10]
add r4,r1,r3
vneg.f32 q10,q10
add lr,r1,lr
vld1.32 {d16-d17}, [r9]
add ip,r1,ip
vneg.f32 q8,q8

Больше информации:

  • GCC 4.9.2 для Raspbian
  • флаги компилятора: -c -fPIE -march=armv7-a -Wall -O3 -mfloat-abi=hard -mfpu=neon

Когда я пишу цикл в коде ASM, построенном в точном соответствии с моими внутренними функциями (даже без использования дополнительных регистров src / dest для получения некоторых свободных циклов ARM), он все равно быстрее, чем код GCC.

Обновление: я ценю ответ Джеймса, но, по сути, это не помогает решить проблему. Самые простые из моих функций работают немного лучше с опцией cortex-a7, но большинство не изменилось. Печальная правда заключается в том, что оптимизация встроенных функций GCC невелика. Когда я работал с компилятором Microsoft ARM несколько лет назад, он постоянно создавал хорошо продуманный вывод для встроенных функций NEON, в то время как GCC постоянно спотыкался. В GCC 4.9.x ничего не изменилось. Я, конечно, ценю FOSS-характер GCC и большие усилия GNU, но нельзя отрицать, что он не так хорош, как компиляторы Intel, Microsoft или даже ARM.


person BitBank    schedule 20.01.2016    source источник
comment
Я понятия не имею. Рассмотрите возможность сообщения об ошибке компилятора или написания сборки напрямую. Обычно встроенные функции обрабатываются как обычные встроенные функции. Нет гарантии, что компилятор выдает инструкции в том же порядке, в котором были написаны встроенные функции.   -  person fuz    schedule 20.01.2016
comment
Полезной информацией будет версия GCC, которую вы используете вместе с процессором, для которого вы настраиваете (-mcpu = ??? или -mtune = ???). В общем, ответ таков, потому что GCC считает, что используемое им чередование приводит к лучшему использованию процессора, чем чередование, которое вы просили. Другой вопрос, как вы определяете, что есть киоски?   -  person James Greenhalgh    schedule 20.01.2016
comment
@James см. Выше информацию о компиляторе. Я написал asm-код в своем стиле, и он быстрее, чем код GCC. Целевой ЦП - Cortex-A7.   -  person BitBank    schedule 20.01.2016
comment
Raspberry Pi поддерживает Neon ?! Цвет меня удивил.   -  person fuz    schedule 20.01.2016
comment
как насчет попытки сначала отключить оптимизацию GCC?   -  person coredump    schedule 20.01.2016
comment
@coredump - отключение оптимизации заставляет его производить еще более медленный код, который по-прежнему портит встроенные функции.   -  person BitBank    schedule 20.01.2016
comment
Если хотите написать сборку, напишите сборку. В любом случае это более читабельно.   -  person Stephen Canon    schedule 20.01.2016
comment
@StephenCanon - Я был бы счастлив написать код ASM, но, как я уже сказал, для переносимости (32/64-битная ARM) и ремонтопригодности будущими разработчиками, не знающими языка ассемблера, были выбраны встроенные функции.   -  person BitBank    schedule 20.01.2016
comment
Попробуйте явное -mcpu=cortex-a7, чтобы изменить модель планирования инструкций, которую использует компилятор. Если вы хотите попробовать более экстремальный флаг, вы можете попросить GCC вообще не пытаться планировать выполнение инструкций с -fno-schedule-insns -fno-schedule-insns2.   -  person James Greenhalgh    schedule 20.01.2016
comment
Ну, -mtune=cortex-a7 (Linaro GCC 5.1) делает вывод очень похожим на ввод ...   -  person Notlikethat    schedule 20.01.2016
comment
@JamesGreenhalgh Спасибо за предложения. Установка -mcpu = cortex-a7 заставила его генерировать лучший код, который намного лучше чередовал инструкции и ускорил работу. Обе опции -fno-schedule замедляли вывод.   -  person BitBank    schedule 20.01.2016
comment
gcc просто следует стандарту C, который позволяет оптимизировать код в строгом соответствии с абстрактной машиной. Прежде чем компилятор выполнит свою работу, вы должны сначала узнать, что ему разрешено делать. Если вам нужен полный контроль, используйте Ассемблер! И что заставляет вас думать, что неоновые расширения более портативны, чем другие инструкции? Обратите внимание, что по мере того, как вы движетесь к скорости, ARM64 в любом случае имеет совершенно другой конвейер и внутреннюю структуру, поэтому вся оптимизация в любом случае зависит от процессора (даже между разными ядрами ARMv7A есть некоторые различия).   -  person too honest for this site    schedule 20.01.2016
comment
@JamesGreenhalgh - К вашему сведению, я также работаю с Dragonboard 410c и ARM64 Linux. Я видел некоторое поведение компилятора, которое заставило меня поверить в то, что в версии GCC для ARM64 могут быть ошибки. Если вы не возражаете, свяжитесь со мной, чтобы узнать больше ([email protected]).   -  person BitBank    schedule 20.01.2016
comment
@BitBank Если это возможно для вас, о плохом (или неожиданном) поведении GCC лучше всего сообщать через общие списки рассылки разработчиков GCC [email protected] или сообщая об ошибке в GCC Bugzilla gcc.gnu.org/bugzilla. Он привлекает больше внимания к вопросу, чем только мой, и заархивированные ответы полезно указывать в будущем :-).   -  person James Greenhalgh    schedule 20.01.2016
comment
Я не могу догнать все прочитанное, но, если нужно, нельзя ли поставить asm volatile(""); между каждым внутренним? это перестанет перемещать вещи.   -  person auselen    schedule 20.01.2016
comment
@Olaf - ARM требовала обратной совместимости для своих встроенных функций, поэтому, хотя мнемоника инструкций изменилась для Aarch64, исходный набор встроенных функций правильно компилируется на 32-битных и 64-битных компиляторах ARM. Вот что я имел в виду под 32/64-битной совместимостью.   -  person BitBank    schedule 20.01.2016
comment
Вы упустили суть.   -  person too honest for this site    schedule 20.01.2016
comment
@BitBank вы нашли решение своей проблемы? В новой версии GCC улучшен код?   -  person killdaclick    schedule 31.12.2019
comment
@killdaclick - новые версии GCC на ARM стали лучше, но лично я в основном использую LLVM, и он всегда опережает GCC с точки зрения качества компилятора. Это не было проблемой для проектов, над которыми я недавно работал.   -  person BitBank    schedule 01.01.2020


Ответы (1)


В широком смысле класс оптимизации, который вы здесь видите, известен как «планирование инструкций». GCC использует планирование инструкций, чтобы попытаться построить лучшее расписание для инструкций в каждом базовом блоке вашей программы. Здесь «расписание» относится к любому правильному порядку инструкций в блоке, а «лучшее» расписание может быть таким, которое позволяет избежать остановок и других опасностей конвейера, или тем, которое сокращает текущий диапазон переменных (что приводит к лучшему распределению регистров. ) или какой-либо другой цели заказа в инструкции.

Чтобы избежать остановок из-за опасностей, GCC использует модель конвейера процессора, на который вы нацеливаетесь (см. здесь для получения подробной информации об используемом для них языке спецификации и здесь для примера модели трубопровода). Эта модель дает некоторое представление об алгоритмах планирования GCC функциональных блоков процессора и характеристиках выполнения инструкций на этих функциональных блоках. Затем GCC может планировать инструкции, чтобы минимизировать структурные опасности из-за того, что несколько инструкций требуют одних и тех же ресурсов процессора.

Без параметра -mcpu или -mtune (для компилятора) или параметра --with-cpu или --with-tune (для конфигурации компилятора) GCC для ARM или AArch64 попытается использовать репрезентативную модель для версии архитектуры, на которую вы нацеливаетесь. В этом случае -march=armv7-a заставляет компилятор пытаться запланировать инструкции, как если бы -mtune=cortex-a8 были переданы в командной строке.

Итак, то, что вы видите в своем выводе, - это попытка GCC преобразовать ваш ввод в расписание, которое, как ожидается, будет хорошо выполняться при работе на Cortex-A8 и достаточно хорошо на процессорах, реализующих архитектуру ARMv7-A.

Чтобы улучшить это, вы можете попробовать:

  • Явная установка целевого процессора (-mcpu=cortex-a7)
  • Полное отключение планирования инструкций (`-fno-schedule-insns -fno-schedule-insns2)

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

Изменить. Что касается вашего редактирования, об ошибках производительности в GCC можно сообщить в GCC Bugzilla (см. https://gcc.gnu.org/bugs/) точно так же, как могут быть ошибки корректности. Естественно, при всех оптимизациях присутствует некоторая степень эвристики, и компилятор может быть не в состоянии победить опытного программиста на ассемблере, но если компилятор делает что-то особенно вопиющее, это стоит выделить.

person James Greenhalgh    schedule 20.01.2016