Как помешать GCC взломать мою природу NEON?

c gcc arm neon intrinsics

1761 просмотра

1 ответ

Мне нужно написать оптимизированный код 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 для Распбиана
  • флаги компилятора: -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 ничего не изменилось. Я, конечно, ценю природу FCC GCC и большие усилия GNU, но нельзя отрицать, что он не так хорошо работает, как Intel, Microsoft или даже компиляторы ARM.

Автор: BitBank Источник Размещён: 15.10.2019 05:17

Ответы (1)


9 плюса

Решение

В целом, класс оптимизации, который вы видите здесь, известен как «планирование инструкций». 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/ ) точно так же, как об ошибках правильности. Естественно, что при всех оптимизациях присутствует некоторая степень эвристики, и компилятор может быть не в состоянии превзойти опытного программиста на ассемблере, но если компилятор делает что-то особенно вопиющее, его стоит выделить.

Автор: James Greenhalgh Размещён: 20.01.2016 02:39
Вопросы из категории :
32x32