2016-10-07 13 views
9

Редактировать 3: Изображения являются ссылками на полноразмерные версии. Извините за изображения в тексте, но графики будут трудно скопировать/вставить в текстовую таблицу.Что может привести к тому, что один и тот же код SSE будет работать несколько раз медленнее в той же функции?


У меня есть следующий профиль VTune для программы, составленной с icc --std=c++14 -qopenmp -axS -O3 -fPIC:

VTune profile

В этом профиле, две группы команд, выделены в окне сборки. Верхний кластер занимает значительно меньше времени, чем нижний, несмотря на то, что инструкции идентичны и в одном порядке. Оба кластера расположены внутри одной и той же функции и, очевидно, оба называются n раз. Это происходит каждый раз, когда я запускаю профилировщик, как на Westmere Xeon, так и на ноутбуке Haswell, который я использую прямо сейчас (скомпилирован с SSE, потому что сейчас я нацеливаюсь и участвую в обучении).

Что мне не хватает?

Игнорируйте плохую параллельность, это, скорее всего, связано с дросселированием ноутбука, поскольку на настольном компьютере Xeon этого не происходит.

Я считаю, что это не пример микро-оптимизации, так как эти три вместе составляют достойное% от общего времени, и меня действительно интересует возможная причина такого поведения.

Edit:OMP_NUM_THREADS=1 taskset -c 1 /opt/intel/vtune...

VTune profile

же профиль, хотя и с несколько более низким ИПЦ на этот раз.

+0

выравнивание данных –

+0

@SegFault Тогда это было бы в время загрузки из памяти или кэша в регистрах, который является 'movapsx' инструкция здесь?. это всего лишь операции FPU, насколько я понимаю. – iksemyonov

+0

Многопоточность? Ваше изображение трудно читать, но, возможно, FPU занят другими вещами в одном из случаев ... –

ответ

3

Счетчики HW perf обычно заряжают стойла инструкциям, которым приходилось ждать своих входов, а не инструкции, которые медленно производили выходы.

Входы для вашей первой группы поступают с вашего сбора. Вероятно, кэш-пропуск очень много, и не стоит ли платить за эти инструкции SUBPS/MULPS/ADDPS. Их входные данные поступают непосредственно из векторных нагрузок voxel[], поэтому сбой при доставке в хранилище приведет к некоторой задержке. Но это всего лишь ~ 10 циклов IIRC, небольшие по сравнению с промахами кеша во время сбора. (Эти недостатки кэша отображаются в виде больших баров для инструкций справа до первая группа, которую вы выделили)

Входы для вашей второй группы поступают непосредственно из загрузок, которые могут отсутствовать в кеше. В первой группе прямыми потребителями нагрузок на кэш-миссию были инструкции для линий, подобных тем, которые устанавливают voxel[0], который имеет действительно большую полосу.

Но во второй группе время промаха кэша в a_transfer[] получает атрибут выделенной группы. Или, если это не промахивание кэш-памяти, возможно, это медленный расчет адреса, так как нагрузкам приходится ждать, пока RAX будет готов.


Похоже, что есть много вы могли бы оптимизировать здесь.

  • вместо магазина/перезагрузкой для a_pointf, просто держать его горячим через итерации цикла в __m128 переменной. Хранение/перезагрузка в источнике C имеет смысл только в том случае, если вы обнаружили, что компилятор делает плохой выбор о том, какой векторный регистр должен разливаться (если у него закончились регистры).

  • вычислить vi с _mm_cvttps_epi32(vf), поэтому ROUNDPS не является частью цепочки зависимостей для индексов сбора.

  • Сделайте voxel соберите себя, перетасовывая узкие нагрузки в векторы, вместо того, чтобы писать код, который копирует в массив, а затем загружается из него. (сбой гарантированного хранения, см. Agner Fog's optimization guides и другие ссылки из тега ).

    Это может быть стоит частично векторизации адрес математику (расчет base_0, используя PMULDQ with a constant vector), поэтому вместо магазина/Reload (~ 5 цикл латентность) у вас есть только MOVQ или два (~ 1 или 2 цикла задержка на Haswell, я забыл.)

    Используйте MOVD для загрузки двух смежных значений short и слейте другую пару во второй элемент с PIN-кодом. Вероятно, вы получите хороший код от _mm_setr_epi32(*(const int*)base_0, *(const int*)(base_0 + dim_x), 0, 0), за исключением того, что сглаживание указателя является неопределенным поведением. У вас может быть хуже код от _mm_setr_epi16(*base_0, *(base_0 + 1), *(base_0 + dim_x), *(base_0 + dim_x + 1), 0,0,0,0).

    Затем разверните 16-разрядные элементы с четырьмя 16-разрядными элементами в 32-битные элементы с помощью PMOVSX и преобразуйте их все в float параллельно с _mm_cvtepi32_ps (CVTDQ2PS).

  • Ваши скалярные LERP не являются авто-векторией, но вы делаете два параллельно (и, возможно, можете сохранить инструкцию, так как вы хотите получить результат в векторе).

  • Вызов floorf() является глупым, а вызов функции заставляет компилятор разливать все регистры xmm в память. Скомпилируйте с помощью -ffast-math или что угодно, чтобы включить его в ROUNDSS или сделать это вручную. Тем более, что вы идете вперед и загружаете поплавок, который вы вычисляете из этого в вектор!

  • Используйте векторное сравнение вместо скалярного prev_x/prev_y/prev_z. Используйте MOVMASKPS, чтобы получить результат в целое число, которое вы можете проверить. (Вас интересуют только нижние 3 элемента, поэтому проверьте его с помощью compare_mask & 0b0111 (true, если установлен какой-либо из 3 разрядов 4-разрядной маски после сравнения для не равных с _mm_cmpneq_ps. См. Версию инструкции double для нескольких таблиц на как это все работает:. http://www.felixcloutier.com/x86/CMPPD.html)

+0

Профессионалы в uni (у нас здесь несколько людей из Азии) рассказали мне о профиле на 200 секунд и увеличьте частоту дискретизации. Что я сделал и получил результаты более высокого уровня, но это требует дальнейшего профилирования. Оптимизации, которые вы указали, наделили меня примерно на 25-30%, я думаю, хотя в новой версии алгоритма в определенных ситуациях мне удалось сократить вес этого кода до минимума. Тем не менее, многому научился! – iksemyonov

+0

@iksemyonov: круто, спасибо за реальные цифры о том, сколько у вас получилось. 200s звучит дольше, чем обычно вам нужно профайлы. 1 секунда, как правило, дает хорошие результаты для микробизнеса одной довольно быстрой функции (при условии, что вы учитываете нарастание частоты процессора). (то есть, по меньшей мере, от 100 к до 10 М повторных вызовов текущей тестируемой функции). Если код, который вы тестируете, занимает много времени, и он затрагивает много памяти, то да, более длинные интервалы проверки потребуются для усреднения по нескольким запускам и позволяют кэшу располагаться к шаблону доступа. –

3

Ну, анализируя код сборки, обратите внимание, что время выполнения отнесено к следующей инструкции - так что данные, которые вы ищете по инструкциям, должны быть тщательно интерпретированы. Существует соответствующая отметка в VTune Release Notes:

Продолжительность приписывается к следующей инструкции (200108041)

Для сбора данных о трудоемких запущенных районах цели, Intel® VTune ™ Amplifier прерывает выполнение целевых потоков и атрибутов времени для контекстного IP-адреса.

Из-за механизма сбора захваченный IP-адрес указывает на инструкцию ПОСЛЕ того, который фактически потребляет большую часть времени. Это приводит к тому, что время работы присваивается следующей команде (или, реже, одной из последующих инструкций) в окне просмотра . В редких случаях это также может привести к неправильному присвоению времени работы в источнике - время может быть ошибочно приписано к исходной строке ПОСЛЕ фактической горячей линии.

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

+0

Спасибо за ссылку! Я осознаю смещение, но теперь смущен насчет его направления. В моем примере более длинные бары действительно вызваны инструкциями 'mul/add/sub'? – iksemyonov

+0

Время команды A будет отображаться как время команды A + 1 - так, да, более длинные бары вызваны инструкциями sub/mul/add на самом деле. – Vital

+0

ОК, это было то, что я принимал, спасибо за разъяснение! Теперь, да, мало осталось: выясните, почему они берут от 2 до 3 раз больше времени во втором кластере, будучи названным одинаковым количеством раз :) – iksemyonov