Быстрый floor()

Тема в разделе "WASM.A&O", создана пользователем cppasm, 1 дек 2008.

  1. bugaga

    bugaga New Member

    Публикаций:
    0
    Регистрация:
    1 июл 2007
    Сообщения:
    361
    а!
    насчот этово у MSVS опция есть /QIfist (интеловский тож ее понимет). Или была по крайней мере в VC6++ (а тож мсявки совсем испортеле плюсы)...
    /add
    кстати, а, изначальный алгос
    Код (Text):
    1. int floor_i32(float x)
    2. {
    3.   int i=x;
    4.   if(i>x) i--;
    5.   return i;
    6. }
    достаточно корректен? каким методы для его теста можно использовать?

    а то intel и gcc наиболее круто уложились в маш код
    intel
    /Ox /arch:SSE
    Код (Text):
    1. _floor_i32  PROC NEAR
    2. ; parameter 1: 4 + esp
    3. .B1.1:
    4.   movss     xmm3, DWORD PTR [esp+4]
    5.   cvtss2si  eax, xmm3
    6.   lea       edx, DWORD PTR [eax-1]
    7.   cvtsi2ss  xmm4, eax
    8.   comiss    xmm3, xmm4
    9.   cmovb     eax, edx
    10.   ret
    GCC 3.4.6
    (-O2 -Os -msse -mregparm=3 -masm=intel -momit-leaf-frame-pointer )
    Код (Text):
    1. _floor_i32:
    2.   movss xmm1, DWORD PTR [esp+4]
    3.   cvttss2si eax, xmm1
    4.   cvtsi2ss  xmm0, eax
    5.   lea edx, [eax-1]
    6.   ucomiss xmm0, xmm1
    7.   cmova eax, edx
    8.   ret
    MS 32-bit C/C++ Compiler Version 14.00.50727.42
    /Ox /arch:SSE
    Вобщем то непонятно что и за'оптимайзил. Так будет правельней если
    да и код не слишком то видоизменился при этом, с опциями
    /Ox /G6 /QIfist
    Код (Text):
    1. tv130 = -8                      ; size = 8
    2. _i$ = 8                         ; size = 4
    3. _x$ = 8                         ; size = 4
    4. _floor_i32 PROC
    5.     sub esp, 8
    6. ; Line 3
    7.     fld DWORD PTR _x$[esp+4]
    8.     fld ST(0)
    9.     fistp   QWORD PTR tv130[esp+8]
    10.     mov ecx, DWORD PTR tv130[esp+8]
    11.     mov DWORD PTR _i$[esp+4], ecx
    12. ; Line 4
    13.     fild    DWORD PTR _i$[esp+4]
    14.     fcompp
    15.     fnstsw  ax
    16.     test    ah, 65  ; 00000041H
    17.     jne SHORT $LN4@floor_i32
    18.     sub ecx, 1
    19. $LN4@floor_i32:
    20. ; Line 5
    21.     mov eax, ecx
    22. ; Line 6
    23.     add esp, 8
    24.     ret 0
    Кстати а 14-ая версия компилера ет какой MSVS?
    а то код как я поглядел практически один в один как у VC 6 :-D, с той лишь
    разницей что c /QIfist я получил варненг
    да уш...
     
  2. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Значит по порядку.
    Вариант Black_mirror страдает той же бедой - приведение к int, что без SSE очень долго.
    14-версия MSVS - это MSVS2005
    Опция /QIfist есть и в 2008, так что её не выкинули.
    Но влияет на весь код - что не хорошо.
    Алгоритм изначальный корректен - тестировать можно логически :)
    При приведении к int округление происходит к нулю, а нам надо к минус бесконечности.
    Соответственно для положительных чисел результат будет одинаковый, а для отрицательных округление будет вверх.
    А нам надо вниз, поэтому вычитаем единицу если округлённый результат больше исходного (для всех отрицательных вычитать нельзя, т.к. целые числа сами к себе и округлятся; если вычесть - будет ошибка).

    Протестировал на AMD варианты с float и double.
    Не знаю почему (на Intel такого нет), но вариант с float рвёт вариант с double на ~15тактов.
    Интересно.

    bugaga
    а каким gcc ты проверяеш?
    в смысле есть нормальный порт под Win32, или ты под Nix компилируеш?

    Кстати Intel'овский компилятор меня расстроил - не понимает 3DNow! даже во встроенном асме :dntknw:

    -= добавлено =-
    Сейчас вот проверил.
    Вариант с QIfist выполняется примерно так же (медленнее на 1-2 такта) чем вариант с float и сложением с magic константой.
     
  3. bugaga

    bugaga New Member

    Публикаций:
    0
    Регистрация:
    1 июл 2007
    Сообщения:
    361
    cppasm
    Из портов пакет Mingw32 & Msys, сравнительно качественный (вроде даж 64-bit версия наличествует), хотя я Cygwin юзаю, *nix framework под винду.

    С cygwin'ом идет GCC 3.4.4, но тут мне дистр SlackWare11 сгодился, так что апгрейдил его до версии 3.4.6 (с прицелом на кросс-компиляние вобщемто).
     
  4. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Вот кстати нашёл интересную игрушку :)
    http://www.codeplay.com/vectorc/index_pc.html
    Демо, но можно зарегистрировать (в общем кто ищет - тот найдёт).
    Оптимизирующий компилятор с поддержкой SSE, SSE2, 3DNow!
    Особенно мне понравилась интерактивная оболочка (по функционалу, по виду почти блокнот).
    В левом окне пишеш исходник или открываеш готовый, выбираеш для какого процессора компилировать и с какой степенью оптимизации, жмёш Compile и в правом окне получаеш исходник на ASM.
    Можно объектники получать - форматов много поддерживает.
    Можно компилировать отдельно по функциям а не весь исходник.
    Выдаёт различные советы по оптимизации - он кстати уверяет что float быстрее double будет :)
    Жалко что забросили его...
    Интересный был по-моему продукт.
     
  5. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    В общем я нашёл вариант который меня устраивает :)

    Вот на asm с использованием 3dNow!:

    Код (Text):
    1. int static __inline f2int_3dnow(float f32)
    2. {
    3.         int i32;
    4.     __asm{
    5.         femms
    6.         movd    mm0,[f32]
    7.         pf2id   mm1,mm0
    8.         pi2fd   mm2,mm1
    9.         pfcmpgt mm2,mm0
    10.         paddd   mm1,mm2
    11.         movd    [i32],mm1
    12.         femms
    13.     }
    14.     return i32;
    15. }
    А вот на Си, рабочий при условии что для float используется формат IEEE574 (компиляция с /QIfist):
    Код (Text):
    1. int static __inline f2int(float f32)
    2. {
    3.     int i32;
    4.     i32  = f32;
    5.     f32 -= i32;
    6.     if(*(int *)&f32 & 0x80000000) i32--;
    7.     return i32;
    8. }
    Получено из начального варианта таким образом:
    if(i32 > f32) i32--; --> if(f32 - i32<0) i32--;
    Ну в принципе это всё, за исключением того что с нулём я сравниваю не во float (это дольше), а проверкой старшего бита числа - если 1 значит отрицательное.
    У предыдущих вариантов выиграло 8-10 тактов.
    Как ни странно выиграло у 3DNow! 2 такта, хотя может это погрешности замеров.
    Правда тут особо распаралеливать нечего.
    Признаюсь сочинил я это не совсем сам :)
    Идея взята из Microsoft'овской функции _ftol2_sse2 из RTL VS2005.
    Если бы они идею не испортили, не пришлось бы вообще сочинять велосипед.
    А так ихняя функция не инлайнится, и плюс к этому напихано опять же манипуляций с FPU Control Word.
    У меня фантазия иссякла, быстрее что-то ничего не получается :)
    PS: ёлки, я MS crt в отладчике изучал, а оказывается есть исходники в поставке :)
     
  6. Black_mirror

    Black_mirror Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2002
    Сообщения:
    1.035
    cppasm
    В таком виде писать сравнение нельзя, потому что 0x80000000 - это тоже ноль, только со знаком минус, понятно что ситуация редкая, но я бы для проверки что float<0 использовал бы if(*(unsigned int *)&f32 > 0x80000000U).
     
  7. Black_mirror

    Black_mirror Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2002
    Сообщения:
    1.035
    Код (Text):
    1.     fld [f32];<-80000000h=0.0
    2.     fist [i32];->0
    3.     fisub [i32];=0
    4.     fstp [f32];->80000000h=0.0
    5.     test [f32],80000000h;=80000000h
    6.     jz .m
    7.     dec [i32];->FFFFFFFFh=-1
    8. .m:
    Вообще если трактовать +0 как "может быть 0 или чуть больше", а -0 как "немного меньше 0", то -1 можно считать правильным результатом.
    Было также обнаружено, что
    +0 + -0 = -0 + +0 = +0 - -0 = +0 * +0 = -0 * -0 = +0,
    а -0 - +0 = +0 * -0 = -0 * +0 = -0
     
  8. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Да, насчёт -0 я в курсе :)
    Я просто решил пренебречь.
    Хотя в принципе можно и заменить на (*(unsigned *)&f32 > 0x80000000U) - машинный код получается почти идентичный.
    Просто вместо
    Код (Text):
    1. test reg32,80000000h
    2. jz  @skip_dec
    получается
    Код (Text):
    1. cmp  reg32,80000000h
    2. jbe  @skip_dec