forum.boolean.name

forum.boolean.name (http://forum.boolean.name/index.php)
-   C++ (http://forum.boolean.name/forumdisplay.php?f=22)
-   -   dword to float4 и обратно (http://forum.boolean.name/showthread.php?t=19690)

Samodelkin 12.02.2015 17:27

dword to float4 и обратно
 
Вопрос касается скорее инлайн ассемблера чем С/С++.
Какими инструкциями (в том числе доступными в расширениях процессора) можно максимально быстро выполнять конвертирование dword->float4 и обратно? В частности это нужно для софтварной имплементации выборки из текстуры, обсчёт на xmm регистрах и запись обратно в текстуру.

mr.DIMAS 13.02.2015 00:46

Ответ: dword to float4 и обратно
 
То есть тебе нужно что-то типа такого:
Код:

dword( byte1, byte2, byte3, byte4 )
byte1 -> float( 0...1 )
byte2 -> float( 0...1 )
byte3 -> float( 0...1 )
byte4 -> float( 0...1 )

и обратной запаковки в dword?

опиши подробнее, а то я ничего не понял.

Samodelkin 13.02.2015 03:26

Ответ: dword to float4 и обратно
 
Цитата:

Сообщение от mr.DIMAS (Сообщение 293208)
То есть тебе нужно что-то типа такого:
Код:

dword( byte1, byte2, byte3, byte4 )
byte1 -> float( 0...1 )
byte2 -> float( 0...1 )
byte3 -> float( 0...1 )
byte4 -> float( 0...1 )

и обратной запаковки в dword?

опиши подробнее, а то я ничего не понял.

Да именно так.

В SSE есть инструкция которая из 4 dword'ов может в 4 float'а сконвертить. Но побайтово SSE вроде не умеют работать (только с 4 байтами через инструкцию смешивания). Разделить dword на байты можно через РОН. Но копировать напрямую из РОН в xmm тоже вроде нельзя. Получается нужно загрузить в РОН, разделить на байты, снова записать в память и от туда загрузить в xmm. В целом записываться будет в кеш L1, так что не так много времени займет, но всё же лишние действия. Так или иначе сейчас у меня код написан на С++ и судя по листингу с ассемблером там очень много кода (т. к. делается через FPU), так что возможно удастся сделать быстрей.

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

mr.DIMAS 13.02.2015 16:24

Ответ: dword to float4 и обратно
 
А не проще ли забить на используемую память? Хранить сразу 4 флоата вместо dword? По памяти всего в 4 раза возрастает расход, но убирается ебля с преобразованиями.

Mr_F_ 13.02.2015 16:52

Ответ: dword to float4 и обратно
 
преобразование обычных регистров в XMM и обратно операция столь долгая, что может потеряться профит от самих SSE вычислений.

Samodelkin 13.02.2015 17:06

Ответ: dword to float4 и обратно
 
Цитата:

Сообщение от mr.DIMAS (Сообщение 293222)
А не проще ли забить на используемую память? Хранить сразу 4 флоата вместо dword? По памяти всего в 4 раза возрастает расход, но убирается ебля с преобразованиями.

Технически конечно проще. Такой вариант тоже рассматривается, пока нету больших проблем с занимаемой текстурами памяти и их можно увеличить. Но это также нагрузит и шину памяти, у неё тоже есть предел, ведь кол-во данных для чтения/записи увеличится в 4 раза. Хотя я раньше проводил тесты с выборкой одной текстуры, но сейчас в проекте используется больше чем одна и нагрузка может быть в разы больше. А в современных системах именно память самый медленный элемент. Так что все усилия по simd оптимизации и многопоточности могут упереться в скорость памяти.

К слову даже на видеокартах, где память в разы быстрее, пытаются ужать и текстуры и гбуфер, там даже за счёт сжатия текстур можно сгенерировать часть данных на ходу, не читая их вообще.

Ещё, большие текстуры хуже умещаются в кеше. На самом деле с текстурами вообще такая проблема, что их выборка часто идёт хаотично, поэтому случаются кеш-промахи, здесь я буду пробовать создавать алгоритм наподобие mip-maping'а -- он не только улучшает визуализацию текстур (хотя в моём случае нужно применять и другие методы) но суть мип-мэпинга это подобрать разрешение текстуры таким образом чтобы оно совпадало с разрешением экрана, а это означает что выборка (при сканлайн-рендере) будет более упорядочена и префетчер лучше справится с кешированием данных текстуры. Однако в моём случае (речь о рейкастинге) рендер происходит вертикальными слайсами (столбцами) и это тоже негативно влияет на кеширование, как вариант можно сохранять данные текстур (и буферов) не построчно, а столбцами.

Ну короче тут много о чём можно рассуждать, но лучше подождать когда будут объективные данные с производительностью разных тестов.

Цитата:

Сообщение от Mr_F_
преобразование обычных регистров в XMM и обратно операция столь долгая, что может потеряться профит от самих SSE вычислений.

Ну то есть через кеш L1 это передается? Ну не то чтоб очень дорогая, например по сравнению с чтением из памяти без префетча это много быстрее. Чтение из L1 - около 3 циклов, из L2 около 20 циклов, из памяти - 200 циклов.
Сейчас я делаю инлайн целого ассемблерного блока в несколько страниц длиной, один раз загружаю в начале и записываю в конце, в целом удаётся уместить вычисления в 8 xmm регистров. В SSE есть ручное управление префетчем, так что можно и его попробовать если что.

Samodelkin 18.02.2015 22:55

Ответ: dword to float4 и обратно
 
Итак, первую версию велосипеда я сделал. Надо сказать что SIMD оптимизацию я представлял более оптимистично. Результат конечно есть, но это не разы, это где-то +30%, и это требует значительного пересмотра логики кода и структуры данных. Для сравнения DOD (Data Oriented Design) подход дал мне больше скорости чем ассемблирование (при этом он делает код более простым и понятным, в отличие от ассемблирования). Так или иначе это первая версия кодов, а у меня особого опыта ассемблирования тоже пока нет, возможно дальнейшие улучшения дадут больше результата. Ну и я думаю что поэтапное представление модернизации кодов будет интересно, чтобы наглядно увидеть какие проблемы пришлось решать.

Значит сейчас пойдет речь как раз о извлечении dword значения цвета в формате ARGB и преобразовании его в 4D вектор в формате float4( R, G, B, A ). Вот исходный материал:
Код:

texEnvironmentColor = cpTextureArray[ 0 ]->TexNearFast( { cWallTexCoordVecU[ 0 ], cWallTexCoordVecV[ 0 ] }, cpTextureArray[ 0 ]->ComputeLvl( cRayLenArray[ 0 ], cTwoTanAlpha, cFrameBufferSizeX, cWallWidth ) );
pMem->environmentColorVec0[ 0 ] = static_cast< uint8_t >( texEnvironmentColor >> 16 ) / 255.0f;
pMem->environmentColorVec0[ 1 ] = static_cast< uint8_t >( texEnvironmentColor >> 8 ) / 255.0f;
pMem->environmentColorVec0[ 2 ] = static_cast< uint8_t >( texEnvironmentColor ) / 255.0f;

Код конечно может быть перегружен, из-за того что я вырвал его из контекста, к сожалению сейчас основная цель у меня это сам проект, поэтому я просто не успеваю подготовить какие то сравнительные тесты и т. п. что по правилам следует делать в таких ситуациях. Значит первая строка это выборка из текстуры: не нужно вдаваться в подробности выборки, главное что в переменную texEnvironmentColor сохраняется dword значение цвета. Затем в трех последующих строках делается сдвиг и урезание до одного байта чтобы получить каждое значение цвета по очереди. Тут нужно заметить что альфа мне в данном случае не нужна и я не трачусь на неё, но вообще её получают тем же образом, если она есть в текстуре четвёртым каналом. Затем происходит деление каждого значения на 255.0f. Буква f указана явно (я её вообще всегда указываю) чтобы левый операнд сначала преобразовался к float (тут идёт неявное преобразование), и затем поделился на 255, таким образом мы ограничили значение цвета в диапазоне [0.0f; 1.0f] (вроде как можно применить термин нормализация, раз это вектор). Затем мы присваиваем полученные значения вектору pMem->environmentColorVec0, который уже там выровнен должным образом и всё такое.

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

Этот кусок удобно разделить на две части: разбиение одного dword на четыре dword и преобразование четырёх dword в четыре float (ну как я уже сказал альфы в данном случае нет, но в целом мы работаем с 4 компонентами). В описании к SSE я нашёл полезную инструкцию cvtdq2ps -- конвертирует 4x dword в 4x float (также есть и обратная cvtps2dq если чо). Поэтому вторую часть алгоритма я заассемблировал так:
Код:

asm( "movaps  xmm6, %1  \n\t"       
    "movaps  xmm0, %2  \n\t"
    "cvtdq2ps xmm0, xmm0 \n\t"
    "divps    xmm0, xmm6 \n\t"
    "movaps  %0,  xmm0 "

    : "=m"( pMem->environmentColorVec0 )
    : "m"( pMem->power ),
      "m"( pMem->swap ) );

В xmm6 сразу помещаем вектор из 255, достаточно один раз если не будете затирать регистр. Затем конвертируем, нормализуем и сохраняем в память. Этот участок кода однозначно работает быстрее -- я проверил.

Небольшое отступление: вот кстати есть movaps для выровненных данных, есть movups для невыровненных, и есть допустим addps у которой второй аргумент может быть прочитан из памяти, но не указано выровнен он или нет. Сразу возникает вопрос как лучше?: сначала загрузить через movaps и затем складывать или сразу пользоваться addps? (Видимо нужно смотреть мануалы к процессорам -- там должно быть написано сколько циклов занимают те или иные инструкции на разных процессорах...)

С первой же частью сложнее -- сдвиги я выполнял на регистрах общего назначения (РОН) и как мне кажется получился оверхед:
Код:

asm( "mov      eax,  %3  \n\t"
    "xor      ebx,  ebx  \n\t"
    "mov      bl,  al  \n\t"
    "mov      %0,  ebx  \n\t"
    "shr      eax,  8    \n\t"
    "mov      bl,  al  \n\t"
    "mov      %1,  ebx  \n\t"
    "shr      eax,  8    \n\t"
    "mov      bl,  al  \n\t"
    "mov      %2,  ebx  "

    : "=m"( pMem->swap[ 2 ] ),
      "=m"( pMem->swap[ 1 ] ),
      "=m"( pMem->swap[ 0 ] )
    : "r"( texEnvironmentColor )
    : "eax", "ebx" );

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

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

Ещё есть некоторые соображения насчёт вставки ассемблера в код С/С++. В GCC используется так называемый Extended Asm, крайне неудобная и запутанная вещь. НО, как мне кажется это следствие, а не причина, потому что причина в самом факте что нужно вставлять код ассемблера в код высокоуровневого языка. Компилятор не знает что внутри вставки, а программист пишущий вставку не знает как компилятор будет компилировать код. Предугадать состояние регистрового контекста до вхождения и восстановить его при выходе очень сложно. Если включается отпимизация то практически нереально. К тому же есть всякие ограничения, например на число параметров инлайн вставки. Ящитаю что выходом из этой ситуации будет раздельная компиляция -- С++ код в своём компиляторе, ассемблер в своём (fasm или что-то подобное), затем это вместе слинковывается. В данном случае логично будет оформить асмокод ввиде функции, а в соглашении вызовов есть чёткие правила о инициализации регистров, настройке стека, "сборки мусора" при возвращении из функции и т.д. что предотвратит всякие неожиданности со стороны компилятора. К тому же писание в отдельном файле будет стимулировать создавать большие куски асмокода что минимизирует оверхед на сам вызов этой функции, и кстати наличие большого количества ёмких регистров (даже в относительно старых процессорах) даёт возможность писать по нескольку страниц кода без обращения к памяти. Ну и просто приятный синтаксис с подсветкой в IDE/редакторах созданных специально для ассемблера играет важную роль, ведь от психического состояния программиста зависит конечный результат, насколько код будет качественный.

impersonalis 18.02.2015 23:35

Ответ: dword to float4 и обратно
 
чо за фигня со шрифтом?

Samodelkin 18.02.2015 23:38

Ответ: dword to float4 и обратно
 
Цитата:

Сообщение от impersonalis (Сообщение 293340)
чо за фигня со шрифтом?

Что именно? Некорректно отображатеся или просто не нравится?
Я поставил [font="Comic Sans MS"][color=#303030], так вроде для глаз лучше не? Просто тут такие скучные шрифты...

Phantom 19.02.2015 01:07

Ответ: dword to float4 и обратно
 
Samodelkin, http://lurkmore.to/Comic_Sans

mr.DIMAS 19.02.2015 01:38

Ответ: dword to float4 и обратно
 
2Samodelkin тебе с таким дрочерством надо идти в эмбеддеры - писать прошивки для микропроцессоров, но это так, к слову.

С SSE лучше\удобнее работать на интринсиках, и компилятор заодно оптимизирует SSE код.

Кстати, ты так и не пользуешься оптимизацией компилятора? Ну там может -O3 -msse2 поставить и сравнить скорости велика и то что компилятор сделает?

Samodelkin 19.02.2015 02:53

Ответ: dword to float4 и обратно
 
Цитата:

Сообщение от mr.DIMAS (Сообщение 293346)
2Samodelkin тебе с таким дрочерством надо идти в эмбеддеры - писать прошивки для микропроцессоров, но это так, к слову.

С SSE лучше\удобнее работать на интринсиках, и компилятор заодно оптимизирует SSE код.

Кстати, ты так и не пользуешься оптимизацией компилятора? Ну там может -O3 -msse2 поставить и сравнить скорости велика и то что компилятор сделает?

Не всё так плохо как может показаться -- у меня ассемблер только в двух функциях на несколько страниц кода. Эти функции являются софтварным аналогом пиксельных шейдеров. Если я их сделаю быстрыми -- вся программа станет быстрой. Это вот самый боттлнек, поэтому эти две функции стоят того чтобы над ними поработать. Больше нигде ассемблера нет.

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

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

Оптимизация обфусцирует код. Очень сложно становится отлаживать что-то на уровне ассемблера, и как было показано в презенташке по DOD, компилятор может оптимизировать только 10% кода -- остальные 90% нужно делать программисту. Пока некоторые части моего велосипеда проигрывают, некоторые лучше чем отпимизированный код компилятора, но дело обстоит так исключительно из-за моих недоработок и какого-то незнания ассемблера. Это не финальная версия и я её буду улучшать. Ещё раз повторю это буквально 10-20 страниц на всю программу (где около 10к строк). Так что это никак не задрочество.

Со шрифтами не знал, сорри, попробую подобрать другие =).

mr.DIMAS 19.02.2015 14:06

Ответ: dword to float4 и обратно
 
Цитата:

Очень сложно становится отлаживать что-то на уровне ассемблера,
Отлаживают Debug-билд( -O0 ). А Release-билд( -O3 -msse2 ... ) просто компилят и кидают пользователю. Уж не думаешь ли ты что компилятор поломает твою программу? :-D

Samodelkin 19.02.2015 16:03

Ответ: dword to float4 и обратно
 
Цитата:

Сообщение от mr.DIMAS (Сообщение 293354)
Отлаживают Debug-билд( -O0 ). А Release-билд( -O3 -msse2 ... ) просто компилят и кидают пользователю. Уж не думаешь ли ты что компилятор поломает твою программу? :-D

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

Нужно изначально исходить из того что нужно тестировать две версии -- обычную и оптимизированную. Это две разные программы и их обе тестируют. Часто в оптимизированной находят новые ошибки и проблемы. Также нормальная практика не создавать оптимизированные версии ради экономии времени и бюджета.

mr.DIMAS 19.02.2015 16:52

Ответ: dword to float4 и обратно
 
А всякие сторонние библиотеки ты тоже собираешь без оптимизации, ну например булет?


Часовой пояс GMT +4, время: 21:02.

vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot