Задачка на код без переходов.

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

  1. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Дано:
    EAX - 32-битное число
    ECX - число в диапазоне 0..7 включительно

    Задача:
    Написать функцию возвращающую EAX-1 если EAX<0 и ECX mod 4 == 0, и EAX во всех других случаях.
    При этом не использовать переходы, таблицы и предикатные команды SETcc.
    Скажу сразу - у меня пока решения нет и я вообще не уверен что это возможно.
    Функция оптимизируется по скорости, т.е. желательно обойтись логическими операциями.
    Вот код на Си кто словами не понял :)

    Код (Text):
    1. int func(int x,int i)
    2. {
    3.     if(x<0 && (i & 3)==0) return x-1;
    4.     return x;
    5. }
    Немного подумав пришёл к такому выражению:
    f(x)=x+((x>>31) & k);
    где k должно быть -1 для i=0 или i=4, и 0 в других случаях.
    Вот такую функцию я придумать что-то не могу...
    Точнее могу, но мне не нравится.
    f(x)=x+((x & (~i << 30) & (~i << 31))>>31);
    Может кто-нибудь предложит вариант с меньшим числом операций? :)
     
  2. Stiver

    Stiver Партизан дзена

    Публикаций:
    0
    Регистрация:
    18 дек 2004
    Сообщения:
    812
    Адрес:
    Germany
    cppasm
    Здесь разве не f(x)=x-... должно быть? На одну операцию меньше:

    f(x) = x - (x>>31 & ~(i | (i>>1)) & 1);
     
  3. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    Нет, именно плюс - число знаковое.
    При SAR REG32, 31 в REG32 будет -1 для отрицательных и 0 для положительных.
    Операций столько же.
    В моём варианте инверсия i выполняется дважды - можно вынести за выражение.
    Т.е. сделать инверсию i и дальше везде использовать инвертированное значение.
    Вот ещё вариация, операций столько же:
    f(x)=x+((x & (~((i >> 1) | i) << 31)) >> 31);
     
  4. Stiver

    Stiver Партизан дзена

    Публикаций:
    0
    Регистрация:
    18 дек 2004
    Сообщения:
    812
    Адрес:
    Germany
    Точно, правый шифт тогда signed будет. А по количеству ничего лучше не могу придумать..
     
  5. murder

    murder Member

    Публикаций:
    0
    Регистрация:
    3 июн 2007
    Сообщения:
    628
    Код (Text):
    1. mov edx,eax
    2. and ecx,3
    3. shr edx,cl
    4. sar edx,31
    5. add eax,edx
     
  6. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.792
    cppasm
    mov ebx,ecx
    neg ebx
    and ebx,ecx; выделяем самый левый единичный бит
    cmp ebx,1
    sbb esi,esi; если число в ebx = 0 тогда esi = -1 иначе esi = 0
    cmp ebx,4
    sbb ebx,ebx; если число в ebx <4 тогда ebx = -1 иначе ebx =0
    xor ebx,esi; если число в ecx не кратно 4 тогда ebx=-1 иначе ebx=0
    cmp eax,80000000h
    sbb edx,edx; если число в eax > 0 тогда CF=1 и edx=-1 иначе edx=0
    or ebx,edx
    not ebx
    lea eax,[eax+ebx];EAX-1 если EAX<0 и ECX mod 4 == 0, и EAX во всех других случаях.
    ;При этом не использованы переходы, таблицы и предикатные команды SETcc.
    но у murder код короче
     
  7. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Код (Text):
    1. cdq
    2. and ecx,3
    3. add ecx,edx
    4. adc eax,edx
     
  8. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    murder - прикольно :)
    leo - и как ты такое только придумываеш :)
    У меня такой вариант есть:
    f(x)=x+((x & ((i & 3)-1)) >> 31);

    или то же самое на ASM:
    Код (Text):
    1. and ecx,3
    2. dec ecx
    3. and ecx,eax
    4. sar ecx,31
    5. add eax,ecx
    Вопрос теперь что быстрее.
    Насколько cdq медленная команда?
    По документации она не спаривается, значит не самый лучший выбор...
     
  9. murder

    murder Member

    Публикаций:
    0
    Регистрация:
    3 июн 2007
    Сообщения:
    628
    leo
    Круто! Но там наверное sbb eax,0 вместо adc eax,edx.

    cppasm
    Вроде бы проблема не в cdq, а в adc. Ждём Black_mirror.
     
  10. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    murder
    Не-а. Перенос возникает при (eax < 0)&&(ecx & 3 != 0), т.е. когда eax изменять не нужно - в этом сл. adc eax,edx == (eax-1)+1 == eax

    cppasm
    cdq на современных компах - однотактовая и нормально "спариваемая" (по сути это расширенный аналог movsx)
    А вот adc\sbb - на нормальных процах не более 2-х тактов, на дебильных P4 до 8-ми (?!). Но т.к. это команда последняя, то в итоге все зависит от того как дальше используется рез-т из eax (например, если крутить этот код в цикле, то задержка adc никакой роли играть не будет).
    Кстати в первых моделях P4 (Northwood, которые еще живут и здравствуют) все сдвиги выполняются за 4 такта...

    Твой вариант не слишком хорош тем, что все операции в нем зависимы, т.е. как ни крути, а не менее 5 тактов. Наверняка можно что-то распараллелить
     
  11. murder

    murder Member

    Публикаций:
    0
    Регистрация:
    3 июн 2007
    Сообщения:
    628
    leo
    О великий гуру, ты открыл мне глаза! sub eax,1 это не то же самое, что add eax,-1.

    Буду медитировать над этим.

    Добавлено:
    Ага - логично, всё понял.
     
  12. cppasm

    cppasm New Member

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    923
    А твой с murder не очень хорош тем, что заставить HLL компилятор использовать ADC/SBB это надо постараться. :)
    А писать ассемблерной вставкой - ставить палки в колёса оптимизатору.
    К тому же до и после этого кода идут расчёты на fpu, так что команды эти всё равно идут не подряд, а разбавлены командами от предыдущих и следующих вычислений.

    murder, я кстати тоже поначалу подумал что в конце должно быть не adc eax,edx а sbb eax,edx.
    Но после минуты смотрения на код понял что всё-таки написано правильно :)