LINUX.ORG.RU

Научите SIMD

 , , ,


1

3

Их куча, я запутался.

  • Что наиболее портабельное? windows/linux/android (gcc/mingw/crystalX-gcc)
  • Что наиболее негеморойное?
  • Расширения gcc vs специфика отдельных SIMD https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html
  • Подводные камни, кирпичи и прочее.
  • Примеры статьи ссылки для нубов где уделяется время деталям

Вот такое хочется ускорить


mat4 mat4_mul_mat4(mat4 m1, mat4 m2) {

  mat4 mat;

  mat.xx = (m1.xx * m2.xx) + (m1.xy * m2.yx) + (m1.xz * m2.zx) + (m1.xw * m2.wx);
  mat.xy = (m1.xx * m2.xy) + (m1.xy * m2.yy) + (m1.xz * m2.zy) + (m1.xw * m2.wy);
  mat.xz = (m1.xx * m2.xz) + (m1.xy * m2.yz) + (m1.xz * m2.zz) + (m1.xw * m2.wz);
  mat.xw = (m1.xx * m2.xw) + (m1.xy * m2.yw) + (m1.xz * m2.zw) + (m1.xw * m2.ww);

  mat.yx = (m1.yx * m2.xx) + (m1.yy * m2.yx) + (m1.yz * m2.zx) + (m1.yw * m2.wx);
  mat.yy = (m1.yx * m2.xy) + (m1.yy * m2.yy) + (m1.yz * m2.zy) + (m1.yw * m2.wy);
  mat.yz = (m1.yx * m2.xz) + (m1.yy * m2.yz) + (m1.yz * m2.zz) + (m1.yw * m2.wz);
  mat.yw = (m1.yx * m2.xw) + (m1.yy * m2.yw) + (m1.yz * m2.zw) + (m1.yw * m2.ww);
  
  mat.zx = (m1.zx * m2.xx) + (m1.zy * m2.yx) + (m1.zz * m2.zx) + (m1.zw * m2.wx);
  mat.zy = (m1.zx * m2.xy) + (m1.zy * m2.yy) + (m1.zz * m2.zy) + (m1.zw * m2.wy);
  mat.zz = (m1.zx * m2.xz) + (m1.zy * m2.yz) + (m1.zz * m2.zz) + (m1.zw * m2.wz);
  mat.zw = (m1.zx * m2.xw) + (m1.zy * m2.yw) + (m1.zz * m2.zw) + (m1.zw * m2.ww);

  mat.wx = (m1.wx * m2.xx) + (m1.wy * m2.yx) + (m1.wz * m2.zx) + (m1.ww * m2.wx);
  mat.wy = (m1.wx * m2.xy) + (m1.wy * m2.yy) + (m1.wz * m2.zy) + (m1.ww * m2.wy);
  mat.wz = (m1.wx * m2.xz) + (m1.wy * m2.yz) + (m1.wz * m2.zz) + (m1.ww * m2.wz);
  mat.ww = (m1.wx * m2.xw) + (m1.wy * m2.yw) + (m1.wz * m2.zw) + (m1.ww * m2.ww);

  return mat;

}

И такого много. Будут ли профиты ощутимые? Или нужны более весомые задачи,а на этом профиты будет сожраны накладными расходами?

В целом советам,подсказкам и идеям буду благодарен. Ранее не касался расширений никогда. Сейчас читаю всякое но чем дальше тем больше путанницы. Пока что прихожу к выводу что под хотелки подходит SSE2. Но подумал лучше иметь более одного мнения по этому поводу.

UDP:

Короче в треде оказались правы и такие случаи на x86-64 векторизируются сами по себе хорошо. Так как хотелка заключалась в переносимости то нет смысла прибивать гвоздями к возможностям конкретного процессора а достаточно указывать младшую линейку где нужны нужные оптимизации и указывать её в -mtune и поддерживать соотвецтвенно. Иначе можно получить просто нерабочие исполняемые файлы. На конкретном камне или серверной логике где всё железо известно можно уже извращаться как угодно естественно и выжимать всё что только можно.

Поигрался по разному

dron@gnu:~/555$ gcc-9 -S -m32 -mfpmath=sse -Ofast  -funroll-loops mat.c 
cc1: warning: SSE instruction set disabled, using 387 arithmetics
dron@gnu:~/555$ gcc-9 -S -m32 -mfpmath=sse -Ofast  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S -m64 -mfpmath=sse -Ofast  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -Ofast  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -Ofast  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -O0  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -Ofast  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc -S  -mfpmath=sse -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S   -O3    mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mssse3 -ffast-math -mfpmath=sse  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -mssse3 -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -msse3 -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -ffast-math -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse  -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse  -O3  -march=x86-64  mat.c 
dron@gnu:~/555$ gcc-9 -S   mat.c 
dron@gnu:~/555$ gcc-9 -S  -mfpmath=sse -ffast-math -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -march=x86-64 -O3  -march=native  mat.c 
dron@gnu:~/555$ gcc-9 -S  -mtune=k8-sse3 -O3    mat.c 
dron@gnu:~/555$ gcc-9 -S  -mtune=k8 -O3    mat.c 
...

В текущей сборке с просто -O3 у меня оказалось что уже всё векторизированно. (У меня -03 не вызывает проблем ) и следовательно то что я хотел уже есть. Хотя -mtune=k8делает ещё буст если свести к реальной программе то 34 кадра анимации 1000 объектов подняло до 40 кадров. Это очень не плохо.

Большие же оптимизации в эту сторону равны отсечению целой массы камней что недопустимо для меня (в смысле просто не хоца) Явную оптимизацию никто не отменяет конечно, но вот прямо сейчас в этом конкретном случае я вижу что лучше писать код который не будет вызывать проблем компилятору для его оптимизации чем оптимизировать самому. Не потому что это плохо, а потом у что частная оптимизация руками сразу подразумевает либо доп зависимости либо привязку к железу. А мне этого не хочется. Ну вот как то так. С другой стороны конечно если взять и решить поддерживать k8 и выше то есть смысл вручную использовать SSE2 к примеру. Но это уже разве что в целях обучения и увидеть разницу между руки vs компилятор. Но чуть в сторону и компилятор всё равно будет на коне ибо просто смена опции на haswell при нужном камне естественно порвёт всё как тузик грелку.

На сем. Всё. Всем спасибо и добра.

★★★★★

Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)

Будут ли профиты ощутимые

Мне кааца огромный профит будет

Нет ли готовых либ для матриц? Уж такое точно небось везде ускорено

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от Crocodoom

Нет ли случаем Python-библиотеки для быстрого перемножения матриц с использованием SIMD на всех популярных аппаратных платформах типа x86 ARM и так далее?

I-Love-Microsoft ★★★★★
()
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)
Ответ на: комментарий от i-rinat

Ща попробую, но я тогда сразу на тестовой сцене

LINUX-ORG-RU ★★★★★
() автор топика
  1. портабельно -? (не портабельно вовсе, это под конкретный проц)
  2. негеморойно - (имхо) asm-вставки gcc (или др.хсс , оно не переносимо между компиляторами) 3,4,5) читать доки gcc, Интела по асм-командам, тут anger.org про оптимизацию.

Умножение матриц хорошо ускоряется если одна из них хранится транспонированной.

anonymous
()
Ответ на: комментарий от I-Love-Microsoft

Нет ли готовых либ для матриц? Уж такое точно небось везде ускорено

Я опять Corange ковыряю не хочу тащить сторонее, хочу то что есть подтюнить. Меня всё устраивает. Но вот сейчас натнкулся на тяжёлые расчёты в анимациях надо по возможности ускорить или анимации убирать/упрощать.

Мне кааца огромный профит будет

Эт хорошо, только надо разобраться что к чему. Это с openmp вжух и #pragma-магия сама всё делает, а тут надо аккуратнее.

LINUX-ORG-RU ★★★★★
() автор топика

ИМХО сейчас компиляторы(gcc8+, clang) шикарно такое векторизуют сами, я бы рекомендовал попрактиковать их оптимизации, код придётся возможно лучть подадаптировать и заспать прагмами, например можно взять прагму из OpenMP #pragma omp simd или похожие, как результат вы получите ускоренный код и не придётся переписывать под каждую платформу, минус такого подхода в следующем: не очень высокое качество векторизации под ARM и всё-таки не идеальная оптимизация, но она касается не непосредственно векторизованных вычислений, с этим как раз всё ок, а с тем что на современном x86-64 в случае малых матриц важна оптимизация не только по векторизации а по load/store загрузки и выгрузки данных, а это порой целое искусство и чтобы это делать более менее толково, нужно очень хорошо знать архитектуру платформы и сидеть с жирными профилировщиками типо оного от интел.

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

Либо сразу возьмите glm или Eigen, это ускорит разработку и к тому же под капотом обеих библиотек уже есть все оптимизации и к тому же они более менее переносимы

AKonia ★★
()
Последнее исправление: AKonia (всего исправлений: 1)
Ответ на: комментарий от i-rinat

Попробовал на сцене 1000 анимаций без отрисовки конечно.

33/34 кадра в обычной сборке -O3 34/35 кадра в обычной сборке -ftree-vectorize -03

Чисто грубо по времени профайлер выдаёт топ

dron@gnu:~/egnaroc/demos/animate-benchmark$ gprof ./app
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  Ts/call  Ts/call  name    
 17.73      1.23     1.23                             mat4_mul_mat4

с ftree-vectorize пренебрежительно тоже самое

Да и хочется быть уверенным что там то и там то я использую то-то. Хочется через ifdef две реализации и обычную которая будет работать 100% всегда и везде и ускоренную в том или ином виде.

Ладно. Завтра уже получше проверю. Может накосячил где.

LINUX-ORG-RU ★★★★★
() автор топика
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от AKonia

Спасибо.

pragma omp simd

А я думал оно ещё не готово. Спасибо завтра буду рыть и пробовать

LINUX-ORG-RU ★★★★★
() автор топика
Ответ на: комментарий от LongLiveUbuntu

Да я тоже думал темболее это с openmp просто делать. Но подумалось про simd то раскидывание по ядрам память туда сюда и всё такое. А то выгрузка блоба и вычисления за такт. Показалось интересным попробовать .

LINUX-ORG-RU ★★★★★
() автор топика
Ответ на: комментарий от i-rinat

А….

Тогда надо отдельно сборку делать тестовую и сам тест маленький без O3 и с 03 конкретно mat4 и побобных и глянуть ассемблерный выхлоп без и сы. Может оно уже того самого. Завекторизированно… Но это завтра уже надо.

LINUX-ORG-RU ★★★★★
() автор топика
Ответ на: комментарий от LongLiveUbuntu

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

AKonia ★★
()
Ответ на: комментарий от anonymous

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

AKonia ★★
()

mat4 mat4_mul_mat4(mat4 m1, mat4 m2) {

А что за структуры это, mat4? Может попробовать сделать отдельные массивы, типа

void mat4_mul_mat4(
  float* restrict m1_xx, float* restrict m1_xy, float* restrict m1_xz, float* restrict m1_xw,
  float* restrict m1_yx, float* restrict m1_yy, float* restrict m1_yz, float* restrict m1_yw,
  float* restrict m1_zx, float* restrict m1_zy, float* restrict m1_zz, float* restrict m1_zw,
  float* restrict m1_wx, float* restrict m1_wy, float* restrict m1_wz, float* restrict m1_ww,

  float* restrict m2_xx, float* restrict m2_xy, float* restrict m2_xz, float* restrict m2_xw,
  float* restrict m2_yx, float* restrict m2_yy, float* restrict m2_yz, float* restrict m2_yw,
  float* restrict m2_zx, float* restrict m2_zy, float* restrict m2_zz, float* restrict m2_zw,
  float* restrict m2_wx, float* restrict m2_wy, float* restrict m2_wz, float* restrict m2_ww,

  float* restrict mat_out_xx, float* restrict mat_out_xy, float* restrict mat_out_xz, float* restrict mat_out_xw,
  float* restrict mat_out_yx, float* restrict mat_out_yy, float* restrict mat_out_yz, float* restrict mat_out_yw,
  float* restrict mat_out_zx, float* restrict mat_out_zy, float* restrict mat_out_zz, float* restrict mat_out_zw,
  float* restrict mat_out_wx, float* restrict mat_out_wy, float* restrict mat_out_wz, float* restrict mat_out_ww,

  size_t num) {
  
  // тут дальше что-то умножать и суммировать в цикле и писать результат в mat_out_??, можно сразу много матриц обрабатывать

Которая б принимала на вход просто массивы соответствующих значений конкретных матриц.

Да и вообще, почему б это на GPU не делать? По-моему это как раз идеальная для GPU задача

SZT ★★★★★
()
Последнее исправление: SZT (всего исправлений: 3)

В актуальных компиляторах SIMD включается автоматически (если опциями разрешить использовать соответствующий набор инструкций).

Примеры GCC, CLANG.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 2)
Ответ на: комментарий от AKonia

транспонирование для 4х4 само как умножение по операциям выйдет Да. Поэтому и надо хранить транспонированную(в памяти постолбечно)

интринсики - «те же яйца, только в профиль» не переносимы. Зачем разбираться еще в одном комплекте названий команд?

Если матрицы из float-ов, то лучше использовать SSE2, а не AVX2 (они горячие и проц понижает частоту, пару лет назад обсуждалось). Соответственно пролетает автовекторизация в AVX2 (из ссылок выше).

anonymous
()
Ответ на: комментарий от anonymous

Если матрицы из float-ов, то лучше использовать SSE2, а не AVX2 (они горячие и проц понижает частоту, пару лет назад обсуждалось).

а где конкретно обсуждалось?

ZERG ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

33/34 кадра в обычной сборке -O3 34/35 кадра в обычной сборке -ftree-vectorize -03

У тебя без -ffast-math ничего нормально векторизоваться не будет. Да и автовекторизация говно.

Да и хочется быть уверенным что там то и там то я использую то-то. Хочется через ifdef две реализации и обычную которая будет работать 100% всегда и везде и ускоренную в том или ином виде.

Функции можно перегружать по таргету.

anonymous
()
Ответ на: комментарий от AKonia

транспонирование для 4х4 само как умножение по операциям выйдет, для графики ненужность.

Кто тебе запрещает хранить данные изначально в нужной форме?

Асм вставки это извращенство, уж лучше интринсики если очень хочется целовать векторизацию в пупочек

Чушь нелепая. Не позорься так.

anonymous
()
Ответ на: комментарий от SZT

А что за структуры это, mat4? Может попробовать сделать отдельные массивы, типа

Чушь нелепая. Структура куда лучше. А вообще always_inline нужно сделать, учу.

Которая б принимала на вход просто массивы соответствующих значений конкретных матриц.

Принимала - уже говно.

anonymous
()
Ответ на: комментарий от anonymous

Если матрицы из float-ов, то лучше использовать SSE2, а не AVX2 (они горячие и проц понижает частоту, пару лет назад обсуждалось). Соответственно пролетает автовекторизация в AVX2 (из ссылок выше).

Ага, лучше вообще не включать )

Deleted
()
Ответ на: комментарий от Diano4kaNyashenka

Анимации это частный случай. Есть просто разовые расчёты на cpu c mat4 и подобным. Быстрее расчёты больше запас кадров.

LINUX-ORG-RU ★★★★★
() автор топика
Ответ на: комментарий от LINUX-ORG-RU

На всякий:

  • Автоматически генерируемый SIMD-код, очень часто, на 50-80% состоит из вспомогательных инструкций. В обсуждаемом примере это инструкции для транспонирования матриц (получение всех значений из столбца в одном SIMD-регистре).
  • Поэтому важно, чтобы компилятор мог сокращать эти накладные расходы. Например, посредством пере-использования промежуточных данных.
  • Соответственно, следует использовать LTO, либо все примитивные операции оформлять как inline-функции (поэтому С++ с inline-методами классов очень удобен). Простейшее правило = если функция не содержит более-менее длинного цикла, то она должна быть оформлена inline.

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

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

Deleted
()

> UDP:

> Короче в треде оказались правы и такие случаи на x86-64 векторизируются сами по себе хорошо. Так как хотелка заключалась в переносимости то нет смысла прибивать гвоздями к возможностям конкретного процессора а достаточно указывать младшую линейку где нужны нужные оптимизации и указывать её в -mtune и поддерживать соотвецтвенно. Иначе можно получить просто нерабочие исполняемые файлы. На конкретном камне или серверной логике где всё железо известно можно уже извращаться как угодно естественно и выжимать всё что только можно.

Насколько мне известно, всё не так. Параметр -march указывает минимально необходимый процессор, а -mtune - процессор, поддержка которого нужна опционально. Например, указав -march=x86-64 -mtune=cooperlake, ты получишь работу бинарника на самых первых AMD64/EM64T процессорах, при этом поддержка всех SIMD-инструкций, выпущенных после них, будет опциональная, а не обязательная.

ZenitharChampion ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.