Поиск битов в битборде

Discussion in 'Машинное отделение' started by WildCat, 25 May 2007.

  1. WildCat
    Оффлайн

    WildCat Коршунов Игорь Staff Member

    Репутация:
    0
    Мне для покера нужно реализовать поиск бита в битборде. Вроде у нас тут есть парочка спецов по битбордам. Чего посоветуете?

    Вот самый простой вариант, как мне кажется:
    Code:
    int FirstBit8[256];
    
    void InitFirstBit()
    {
        for (int i = 0; i < 256; i++)
        {
            int p = i;
            FirstBit8[i] = 0;
            for (int j = 0; j < 8; j++)
            {
                if (p & 1)
                {
                    FirstBit8[i] = j;
                    break;
                }
                p >>= 1;
            }
        }
    }
    
    int ExtractBit(uint64 v)
    {
        unsigned char *p = (unsigned char*)&v;
        for (int i = 0; i < 8; i++)
        {
            if (p[i])
            {
                int n = FirstBit8[p[i]];
                p[i] &= ~(1 << n);
                return i * 8 + n;
            }
        }
        return 0;
    }
    Можно еще перейти на 16-битный массив. Есть ли какие принципиально иные подходы?
     
  2. WinPooh
    Оффлайн

    WinPooh В.М. Staff Member

    Репутация:
    95
    Вот самый простой вариант для поиска наименьшего (самого правого) установленного бита, с одновременной установкой его в ноль:

    Code:
    static const FLD LSB_Magic[64] = 
    {
       A8, B4, E1, H5, E8, B2, E2, G5,
       D8, H4, F7, G2, A7, E3, C3, F5,
       C8, C4, F1, C7, E7, A3, G6, F3,
       H8, D4, G1, E6, B6, E4, H1, E5,
       B8, A4, F8, D1, C1, G7, B7, B1,
       A2, D7, D2, H6, A1, F6, C6, H3,
       G4, G8, H7, C2, F2, A5, H2, D6,
       D3, A6, B5, B3, G3, C5, D5, F4
    };
    
    #define pop_lsb(x)  pop_lsb_inner(&x)
    
    static inline FLD pop_lsb_inner(U64* pb)
    {
       if (*pb == 0)
          return NF;
    
       U64 lsb = *pb ^ (*pb - 1);
       unsigned int foldedLSB = ((int) lsb) ^ ((int)(lsb>>32));
       int ind = (foldedLSB * 0x78291ACF) >> (32-6); // range is 0..63
    
       *pb &= ~lsb;
       return LSB_Magic[ind];
    }
    Эта функция не использует ни одной условной операции (ну ладно, только одну - но зато самую часто срабатывающую), требует не очень много дорогих 64-битных операций, и работает практически так же быстро, как и ассемблерные реализации - но при этом написана на чистом Си!
     
  3. NS
    Оффлайн

    NS Нефёдов Сергей баннер

    Репутация:
    3
  4. WinPooh
    Оффлайн

    WinPooh В.М. Staff Member

    Репутация:
    95
    Вообще, на эту тему очень много написано и на WB-форуме, например:
    http://wbforum.vpittlik.org/viewtopic.php?t=3141

    Ну, или в локальный поиск WB-форума - по запросам LSB, FirstOne, magic, de Bruijn и т.п.
     
  5. WildCat
    Оффлайн

    WildCat Коршунов Игорь Staff Member

    Репутация:
    0
    Мне кажется более интересным попробовать использовать BSR и BSF. Только покажите как в Си делать ассемблерные вставки (я никогда этм не занимался).

    Кстати, есть инфа о том, насколько быстро работают BSR и BSF?
     
  6. WinPooh
    Оффлайн

    WinPooh В.М. Staff Member

    Репутация:
    95
    Несколько лет назад я пробовал - выигрыш в скорости поиска от ассемблерной реализации порядка единиц процентов. Иногда и вообще ноль.

    Отказался из-за проблем с портированием, не хотелось для разных компиляторов писать разные функции - у gcc синтаксис ассемблера AT&T, а у MSVC - интеловский. Опять же, юниксовые версии могут вообще на какой-нибудь Альфе запускаться. В общем, получилось бы как у Крафти, универсально, но развесисто и нечитаемо :)
     
  7. NS
    Оффлайн

    NS Нефёдов Сергей баннер

    Репутация:
    3
    Это отсюда:
    http://forum.orenlinux.ru/index.php?topic=271.40

    Так-же есть информация что это самые медленные операции на Pentium/PentumMMX.
    от 7 до 73 тактов! 7 тактов инициализация, и по 2 такта на каждый бит сканирования.

    По более новым процам информацию пока не нашел.
     
  8. WildCat
    Оффлайн

    WildCat Коршунов Игорь Staff Member

    Репутация:
    0
    Деление должно быть медленней :)
     
  9. WildCat
    Оффлайн

    WildCat Коршунов Игорь Staff Member

    Репутация:
    0
    Ладно, я посмотрю как на скорости скажется переход от 8 бит к 16. И тогда будет видно стоит ли это дальнейших оптимизаций.
     
  10. NS
    Оффлайн

    NS Нефёдов Сергей баннер

    Репутация:
    3
    Это к тому что у меня в шашечной ОФ деление в цикле? :)
    Деление 32битных целых чисел в Pentium (div,idiv) чуть больше 40-ка тактов.
     
  11. NS
    Оффлайн

    NS Нефёдов Сергей баннер

    Репутация:
    3
     
  12. WildCat
    Оффлайн

    WildCat Коршунов Игорь Staff Member

    Репутация:
    0
    На винбордном форуме вроде решили, что лучше не связываться с ассемблерными вставками, а вполне нормально можно сделать на Си через таблицы.
     
  13. NS
    Оффлайн

    NS Нефёдов Сергей баннер

    Репутация:
    3
    Закачал документацию по Athlon64.
    64-битный bsf выполняется за 9 тактов.
    Скорей всего быстрее сделать невозможно.
     
  14. Counter
    Оффлайн

    Counter Учаcтник

    Репутация:
    0
    int FirstOne(UInt64 value)
    {
    __asm
    {
    bsf eax, dword ptr value
    jnz l
    bsf eax, dword ptr value + 04h
    add eax, 20h
    l:
    }
    }

    int PopFirstOne(UInt64 &value)
    {
    int firstOne = FirstOne(value);
    value &= value - 1;
    return firstOne;
    }
     
  15. NS
    Оффлайн

    NS Нефёдов Сергей баннер

    Репутация:
    3
    А почему не сделать так-же?
    btr, 2 такта.
     
  16. Booot
    Оффлайн

    Booot Учаcтник

    Репутация:
    0
    Я пользуюсь BSF и BSR. Просто потому что мне так оказалось удобнее. Тащить таблицы только из-за того, что лень ассемблер вставлять? Но правда у меня движок на Делфи написан стало быть лишней головной боли с перенесением его на другую платформу быть не должно за не имением :)
     
  17. Counter
    Оффлайн

    Counter Учаcтник

    Репутация:
    0
    А про btr я не знал :) Можно попробовать.
     
  18. WinPooh
    Оффлайн

    WinPooh В.М. Staff Member

    Репутация:
    95
    Всем хорош такой код, только вот при попытке заинлайнить эту функцию компилятор (gcc, во всяком случае) начинает ругаться на повторное определение метки l: :(
     
  19. Vlad_Imir
    Оффлайн

    Vlad_Imir Новичок

    Репутация:
    20
    Code:
        BYTE  inline GetLastBit(INT_64 line)
        {
            __asm {
                bsr rax, qword ptr line
            }
        }
        BYTE  inline GetFirstBit(INT_64 line)
        {
            __asm {
                bsf rax, qword ptr line
            }
        }
    Использую для 64 битной версии, MS Visual Studio 2005 + Intel Compiller
     
  20. Осипов Юрий
    Оффлайн

    Осипов Юрий Учаcтник

    Репутация:
    11
    В свете вышеизложенного 64-битного кода.
    Было выпущено несколько 64-битных версий Стрелки. Интересно, догадались эти компилировщики изменить функции first_one и last_one, а заодно popcnt, или скомпилировали как есть?
    Был бы я таким специалистом по дизассемблированию, как обо мне думают, то проверил бы. Может, кто-то знает, что было сделано?
     
  21. Bison
    Оффлайн

    Bison Учаcтник

    Репутация:
    0
    Думаю, что как есть просто не скомпилировалось бы... Синтаксис 64-битного ассемблера другой
     
  22. bankuss
    Оффлайн

    bankuss Александр баннер

    Репутация:
    6
    Осипов Юрий, 64 битная студия исходный код "Стрелки" не компилирует! Ошибка выдается как раз на 32-разрядные асм-вставки. Переделав их на х64 все компилируется прекрасно.
     
  23. Осипов Юрий
    Оффлайн

    Осипов Юрий Учаcтник

    Репутация:
    11
    Непонятно - откуда такая дискриминация.
    Я не знаю, в чем специфика 64-битных приложений.
    Но в 32-битных всегда допускались операции с 16-разрядными и 8-разрядными данными.
    На что может ругаться 64-битный компилятор, если встречает операцию над 32-битными данными?
     
  24. bankuss
    Оффлайн

    bankuss Александр баннер

    Репутация:
    6
    Осипов Юрий видимо опкоды х64 другие и студия ругается на это.