советы по оптимизации при фиксировании значения в цикле - PullRequest
0 голосов
/ 16 января 2019

У меня плотная петля, точно такая же, как у Чендлера Каррута, представленного в CPP CON 2017: https://www.youtube.com/watch?v=2EWejmkKlxs через 25 минут в этом видео есть такая петля:

for (int& i:v)
    i = i>255?255:i;

, где v - вектор. Это точно такой же код, который используется в моей программе, который после профилирования занимает много времени.

В своей презентации Чендлер изменил сборку и ускорил цикл. Мой вопрос на практике в производственном коде, каков рекомендуемый подход для оптимизации этого? Должны ли мы использовать встроенную сборку в коде C ++? Или, как Чендлер, скомпилировать код C ++ в сборку, а затем оптимизировать ассемблер?

Пример оптимизации вышеприведенного цикла for будет по-настоящему оценен при условии архитектуры x86.

Ответы [ 2 ]

0 голосов
/ 16 января 2019

Чендлер изменил вывод asm компилятора, потому что это простой способ провести одноразовый эксперимент, чтобы выяснить, будет ли изменение полезно , без , выполняя все, что вы ' Обычно я хочу включить цикл asm или функцию как часть исходного кода для проекта.

Генерируемый компилятором asm, как правило, является хорошей отправной точкой для оптимизированного цикла, но фактически сохранение всего файла как есть, не является хорошим или даже жизнеспособным способом фактически поддерживать реализацию цикла asm как часть программы. , См. Ответ @ Аконкагуа.

Кроме того, он отрицает необходимость иметь какие-либо другие функции в файле, написанном на C ++, и быть доступным для оптимизации во время компоновки.


Re: фактически зажим:

Обратите внимание, что Чендлер просто экспериментировал с изменениями не векторизованного кода и отключил развертывание + авто-векторизацию. Надеемся, что в реальной жизни вы можете выбрать SSE4.1 или AVX2 и позволить компилятору автоматически векторизовать pminsd или pminud для зажима int со знаком или без знака до верхней границы. (Также доступно для элементов других размеров. Или без SSE4.1, только SSE2, может быть, вы можете 2x PACKSSDW => packuswb (без знака насыщения), а затем распаковать с нулями до 4 векторов меча элементы. (Если вы не можете просто использовать вывод uint8_t[]!)

И кстати, в комментариях к видео Чендлер сказал, что оказывается, что он допустил ошибку, и эффект, который он увидел, не был на самом деле из-за предсказуемой ветви против cmov. Возможно, это было связано с выравниванием кода, потому что изменение с mov %ebx, (%rdi) на movl $255, (%rdi) имело значение!

(ЦП AMD, как известно, не имеют регистров чтения с задержками, как у семейства P6, не должно возникнуть никаких проблем с сокрытием цепочки dep в cmov, связывающей хранилище с нагрузкой, против разрыва с предсказанием ветвлений + предположениями прошлого филиал.)


Вы очень редко действительно хотите использовать рукописный цикл. Часто вы можете удерживать и / или обманывать свой компилятор, чтобы сделать asm более похожим на то, что вы хотите, просто изменив исходный код C ++. Тогда будущий компилятор может настраиваться по-другому для -march=some_future_cpu.

0 голосов
/ 16 января 2019

Мой вопрос, на практике, в рабочем коде, каков рекомендуемый подход для его оптимизации? Должны ли мы использовать встроенную сборку в коде C ++? Или, как Чендлер, скомпилировать код C ++ в сборку, а затем оптимизировать ассемблер?

Для производственного кода необходимо учитывать, что программное обеспечение может быть скомпилировано и связано в автоматической системе сборки.

Как бы вы хотели применить изменения кода к коду ассемблера в такой системе? Вы можете применить файл diff, но он может сломаться, если настройки оптимизации (или другие) изменились, если переключиться на другой компилятор или ...

Теперь осталось две опции: написать всю функцию в файле ассемблера (.s) или иметь встроенный код ассемблера внутри кода C ++ & ndash; последний, возможно, с преимуществом сохранения связанного кода в той же единице перевода.

Тем не менее, я бы позволил компилятору генерировать ассемблерный код один раз & ndash; с наивысшим доступным уровнем оптимизации. Затем этот код может служить (уже предварительно оптимизированным) базой для ваших оптимизаций, сделанных вручную, результаты которых затем должны быть вставлены обратно в виде встроенной сборки в исходный файл C ++ или помещены в отдельный исходный файл сборки.

...