Keresse meg az első készletet - Find first set

A számítógépes szoftver és hardver, megtalálni első ( FFS ), vagy talál elsőt egy kicsit művelet , hogy mivel egy aláíratlan gépi szó , kijelöli az index vagy pozícióját a legkisebb helyiértékű állítva egy a szót attól a legkisebb helyi értékű bit pozíció. Egy majdnem egyenértékű művelet a számláló záró nullák ( ctz ) vagy a záró nullák száma ( ntz ), amelyek a legkevésbé szignifikáns bit után számolják a nulla bitek számát. A kiegészítő művelet, amely megtalálja a legjelentősebb halmaz bit indexét vagy pozícióját, a 2 log bázis , így nevezzük, mert kiszámítja a ⌊log 2 (x) b bináris logaritmust . Ez szorosan összefügg a kezdő nullák számával ( clz ) vagy a vezető nullák számával ( nlz ), amely a legjelentősebb egy bit előtti nulla bitek számát számolja. Az első halmaz két általános változata létezik, a POSIX definíció, amely a bitek indexelését 1 -nél kezdi, itt ffs, és az a változat, amely a bitek indexelését nulláról kezdi, ami egyenértékű a ctz -vel, és így fogják hívni.

A legtöbb modern CPU utasításkészlet -architektúra ezek közül egyet vagy többet biztosít hardver operátornak; a szoftveres emulációt általában minden olyan esetében biztosítjuk, amely nem áll rendelkezésre, sem fordító belső tulajdonságaiként, sem a rendszerkönyvtárakban.

Példák

A következő 32 bites szó miatt:

0000 0000 0000 0000 1000 0000 0000 1000

A számláló záró nullák művelete 3-at, míg a nullákat számláló művelet 16-ot ad vissza. A számok vezető nullák művelete a szó méretétől függ: ha ezt a 32 bites szót 16 bites szóvá csonkolnák, akkor az első nullák számlálása nullát adna vissza . Az első készlet megtalálása művelet 4 -et adna vissza, jelezve a 4. pozíciót jobbról. A 2 rönk alapja 15.

Hasonlóképpen, tekintettel a következő 32 bites szóra, a fenti szó bitenkénti tagadása:

1111 1111 1111 1111 0111 1111 1111 0111

A visszaszámláló műveletek 3, az első számok művelete 16, az ffz első nulla művelet 4 értéket adnak vissza.

Ha a szó nulla (nincsenek bitek beállítva), akkor az első nullák és a záró nullák számlálása egyaránt a szó bitjeinek számát adja vissza, míg az ffs nullát. Mind a 2. napló bázis, mind a keresési első halmaz nullaalapú megvalósításai általában egy nulla szó definiálatlan eredményét adják vissza.

Hardver támogatás

Sok architektúra tartalmaz utasításokat az első halmaz és/vagy kapcsolódó műveletek gyors elvégzésére, az alábbiakban felsorolva. A leggyakoribb művelet a kezdő nullák számolása (clz), valószínűleg azért, mert minden más művelet hatékonyan megvalósítható (lásd Tulajdonságok és kapcsolatok ).

Felület Emlékezeterősítő Név Operandus szélességek Leírás Jelentkezéskor 0 -ra
ARM ( ARMv5T architektúra és újabb ),
kivéve a Cortex-M0/M0+/M1/M23
clz Vezető nullák grófja 32 clz 32
ARM ( ARMv8-A architektúra ) clz Vezető nullák grófja 32, 64 clz Operandus szélessége
AVR32 clz Vezető nullák grófja 32 clz 32
DEC Alpha ctlz Vezető nullák grófja 64 clz 64
cttz Záró nullák számlálása 64 ctz 64
Intel 80386 és újabb bsf Bit szkennelés előre 16, 32, 64 ctz Határozatlan; nulla zászlót állít be
bsr Bit Scan Reverse 16, 32, 64 Naplóalap 2 Határozatlan; nulla zászlót állít be
x86 támogatja a BMI1 vagy az ABM lzcnt Vezető nullák grófja 16, 32, 64 clz Operandus szélesség; készletek zászlót viselnek
x86 támogatja a BMI1 -et tzcnt Záró nullák számlálása 16, 32, 64 ctz Operandus szélesség; készletek zászlót viselnek
Itánium clz Vezető nullák grófja 64 clz 64
MIPS clz Számolja a vezető nullákat a Wordben 32, 64 clz Operandus szélessége
clo Számolja a vezetőket a Wordben 32, 64 clo Operandus szélessége
Motorola 68020 és újabb bfffo Keresse meg az elsőt a Bit mezőben Tetszőleges Naplóalap 2 Mezőeltolás + mezőszélesség
PDP-10 jffo Ugorjon, ha megtalálja az elsőt 36 ctz 0; nincs művelet
POWER / PowerPC / Power ISA cntlz/cntlzw/cntlzd Vezető nullák grófja 32, 64 clz Operandus szélessége
Power ISA 3.0 és újabb cnttzw/cnttzd Záró nullák számlálása 32, 64 ctz Operandus szélessége
RISC-V ("B" kiterjesztés) (tervezet) clz Vezető nullák grófja 32, 64 clz Operandus szélessége
ctz Záró nullák számlálása 32, 64 ctz Operandus szélessége
SPARC Oracle Architecture 2011 és később lzcnt (szinonima: lzd) Vezető nulla gróf 64 clz 64
VAX ffs Keresse meg az első készletet 0–32 ctz Operandus szélesség; nulla zászlót állít be
IBM z/Architecture flogr Keresse meg a Leftmost One -t 64 clz 64
vclz Vektor gróf vezető nullák 8, 16, 32, 64 clz Operandus szélessége
vctz Vektor szám nulla után 8, 16, 32, 64 ctz Operandus szélessége

Néhány alfa platformon a CTLZ és a CTTZ szoftverben emulálódik.

Eszköz- és könyvtártámogatás

Számos fordító- és könyvtárgyártó szolgáltatja a fordító belső tulajdonságait vagy könyvtári funkcióit az első készlet és/vagy a kapcsolódó műveletek megkereséséhez, amelyeket gyakran a fenti hardverutasítások alapján hajtanak végre:

Eszköz/könyvtár Név típus Bemeneti típus (ok) Megjegyzések Jelentkezéskor 0 -ra
POSIX .1 kompatibilis libc
4.3BSD libc
OS X 10.3 libc
ffs Könyvtári funkció int Glibc -t tartalmaz . A POSIX nem biztosítja a kiegészítő 2 / clz rönk alapot. 0
FreeBSD 5.3 libc
OS X 10.4 libc
ffsl
fls
flsl
Könyvtári funkció int,
hosszú
fls ("megtalálja az utolsó halmazt") kiszámítja (napló 2) + 1. 0
FreeBSD 7.1 libc ffsll
flsll
Könyvtári funkció hosszú hosszú 0
GCC 3.4.0
Clang 5.x
__builtin_ffs[l,ll,imax]
__builtin_clz[l,ll,imax]
__builtin_ctz[l,ll,imax]
Beépített funkciók unsigned int,
unsigned long,
unsigned long long,
uintmax_t
A GCC dokumentációja az eredményt definiálatlan clz és ctz értékeket veszi figyelembe 0 -n. 0 (ffs)
Visual Studio 2005 _BitScanForward
_BitScanReverse
Fordító belső tulajdonságai aláíratlan hosszú,
aláíratlan __int64
Külön visszatérési érték jelzi a nulla bemenetet Határozatlan
Visual Studio 2008 __lzcnt Belső fordító unsigned short,
unsigned int,
unsigned __int64
A BMI1 -ben vagy az ABM -ben bevezetett lzcnt utasítás hardvertámogatásán alapul . Operandus szélessége
Intel C ++ fordító _bit_scan_forward
_bit_scan_reverse
Fordító belső tulajdonságai int Határozatlan
NVIDIA CUDA __clz Funkciók 32 bites, 64 bites Kevesebb utasítást tartalmaz a GeForce 400 sorozatban 32
__ffs 0
LLVM llvm.ctlz.*
llvm.cttz.*
Belső 8, 16, 32, 64, 256 LLVM összeállítási nyelv Operandus szélessége, ha a 2. argumentum 0; egyébként nem definiált
GHC 7.10 (4.8 bázis), inData.Bits countLeadingZeros
countTrailingZeros
Könyvtári funkció FiniteBits b => b Haskell programozási nyelv Operandus szélessége
C ++ 20 szabványos könyvtár, fejlécben<bit> bit_ceil bit_floor
bit_width
countl_zero countl_one
countr_zero countr_one
Könyvtári funkció unsigned char,
unsigned short,
unsigned int,
unsigned long,
unsigned long long

Tulajdonságok és kapcsolatok

Ha a bitek 1 -től kezdődően vannak megjelölve (ez az ebben a cikkben használt konvenció), akkor a záró nullákat számolja és az első halmaz műveleteit ctz ( x ) = ffs ( x ) - 1 kapcsolja össze (kivéve, ha a bemenet nulla). Ha a biteket 0 -tól kezdődően jelölik , akkor a záró nullák számlálása és az első halmaz megtalálása pontosan egyenértékű műveletek. Ha szónként w bit adódik , a log 2 könnyen kiszámítható a clz -ből, és fordítva log 2 ( x ) = w - 1 - clz ( x ) segítségével .

Amint azt a fenti példa is mutatja, az első nulla keresése, a vezető számok számítása és a számolás után műveletek végrehajthatók úgy, hogy tagadják a bemenetet, és a kereső első halmaz, a vezető nullák számlálása és a záró nullák számlálása funkciót használják. Fordítva is igaz.

A hatékony log 2 műveletű platformokon, mint például az M68000, a ctz kiszámítható:

ctz ( x ) = log 2 ( x & −x )

ahol & jelöli bitenkénti AND és -x jelöli a kettes komplemens az x . Az x & −x kifejezés a legkevésbé jelentős 1 bitet leszámítva törli az összes bitet, így a leg- és legkevésbé szignifikáns 1 bit azonos.

Azon platformokon, ahol hatékonyan vezetnek a nullák, mint például az ARM és a PowerPC, az ffs kiszámítható:

ffs ( x ) = w - clz ( x & −x ) .

Ezzel szemben a log 2 vagy clz operátorok nélküli gépeken a clz kiszámítható a ctz használatával , bár nem hatékonyan:

ClZ = W - CTZ (2 ⌈log 2 ( x ) ⌉ ) (ami függ a CTZ visszatérő w a nulla input)

A platformok hatékony Hamming súlya (lakosság száma) műveletet, például SPARC „s POPCvagy Feketeúszójú ” s ONESvan:

ctz ( x ) = popcount (( x & −x ) - 1) , vagy ctz ( x ) = popcount (~ ( x | −x )) ,
ffs ( x ) = popcount ( x ^ ~ - x )
ClZ = 32 - popcount (2 ⌈log 2 ( x ) ⌉ - 1)

ahol ^ bites soronként kizárólagos-VAGY, | bitrendben VAGY és ~ jelzi bitenkénti tagadást.

Az inverz feladat (adott i-vel x-et állítunk elő , hogy ctz ( x ) = i ) kiszámítható bal eltolással ( 1 << i ).

Az első halmaz megkeresése és a kapcsolódó műveletek egyszerűen kiterjeszthetők önkényesen nagy bit tömbökre is , az egyik végből indulva , és addig folytatva , amíg egy szó nem teljesen nulla ( ffs , ctz , clz ) vagy nem minden egy ( ffz esetén , clo , cto ) találkozik. Ezt felgyorsíthatja egy fa adatstruktúra, amely rekurzív módon bitképeket használ a nem nulla szavak nyomon követésére.

Szoftver -emuláció

A legtöbb processzor az 1980-as évek végétől kezdve rendelkezik bit-operátorokkal az ffs-hez vagy azzal egyenértékűhöz, de néhány modernnek, mint például néhány ARM-Mx sorozatnak nincs. Az ffs, clz és ctz hardveroperátorok helyett a szoftver emulálhatja őket műszakokkal, egész számtani és bitszerű operátorokkal. A CPU felépítésétől és kisebb mértékben a programozási nyelv szemantikájától és a fordítói kód generálásának minőségétől függően többféle megközelítés létezik. A megközelítéseket lazán leírhatjuk lineáris keresésnek , bináris keresésnek , keresés+táblázatkeresésnek, de Bruijn -szorzásnak, lebegőpontos konverziónak/kitevőkivonatnak és bitoperátor (ág nélküli) metódusoknak. Kompromisszumok vannak a végrehajtási idő és a tárhely között, valamint a hordozhatóság és a hatékonyság között.

A szoftver -emulációk általában determinisztikusak. Minden bemeneti értékre meghatározott eredményt adnak vissza; különösen az összes nulla bit bemenetének eredménye általában 0 az ffs esetén, és az operandus bithossza a többi művelethez.

Ha valakinek van hardveres clz -e vagy azzal egyenértékű, akkor a ctz hatékonyan kiszámítható bitműveletekkel, de fordítva nem igaz: a clz hardveres operátor hiányában nem hatékony számításhoz.

2 n

A 2 ⌈log 2 (x) The függvény (kerekítés felfelé a legközelebbi kettes hatványra) műveletek és bites soros OR-ok használatával nem hatékony számításhoz, mint ebben a 32 bites példában, és még kevésbé hatékony, ha 64 bites vagy 128-as -bit operandus:

function pow2(x):
    if x = 0 return invalid  // invalid is implementation defined (not in [0,63])
    x ← x - 1
    for each y in {1, 2, 4, 8, 16}: x ← x | (x >> y)
    return x + 1

FFS

Mivel ffs = ctz + 1 (POSIX) vagy ffs = ctz (egyéb megvalósítások), a ctz -hez alkalmazható algoritmusok használhatók, az utolsó lépés az lehet, hogy 1 -et adnak hozzá az eredményhez, és 0 -t adnak vissza az operandus hossza helyett. minden nulla bit.

CTZ

A kanonikus algoritmus egy ciklusszámláló nulla, amely az LSB-től kezdődik, amíg egy 1 bitet nem talál:

function ctz1 (x)
    if x = 0 return w
    t ← 1
    r ← 0
    while (x & t) = 0
        t ← t << 1
        r ← r + 1
    return r

Ez az algoritmus O (n) időt és műveleteket hajt végre, és a gyakorlatban nem praktikus a feltételes elágazások nagy száma miatt.

A lekérdezési táblázat kiküszöbölheti a legtöbb ágat:

table[0..2n-1] = ctz(i) for i in 0..2n-1
function ctz2 (x)
    if x = 0 return w
    r ← 0
    loop
        if (x & (2n-1)) ≠ 0
            return r + table[x & (2n-1)]
        x ← x >> n
        r ← r + n

Az n paraméter rögzített (jellemzően 8), és idő -tér kompromisszumot jelent . A hurok teljesen feltekeredhet . De lineáris keresésként ez a megközelítés továbbra is O (n) az operandus bitjeinek számában.

A bináris keresés megvalósítása logaritmikus számú műveletet és elágazást igényel, mint ebben a 32 bites verzióban: Ezt az algoritmust tábla is segítheti, és az alsó három "if" utasítást 256 bejegyzés-keresési táblával helyettesíti az első nem -indexként nulla bájt.

function ctz3 (x)
    if x = 0 return 32
    n ← 0
    if (x & 0x0000FFFF) = 0: n ← n + 16, x ← x >> 16
    if (x & 0x000000FF) = 0: n ← n +  8, x ← x >>  8
    if (x & 0x0000000F) = 0: n ← n +  4, x ← x >>  4
    if (x & 0x00000003) = 0: n ← n +  2, x ← x >>  2
    if (x & 0x00000001) = 0: n ← n +  1
    return n

Ha a hardver rendelkezik clz operátorral, a ctz számításának leghatékonyabb módja a következő:

function ctz4 (x)
    x &= -x
    return w - (clz(x) + 1)

A 32 bites ctz algoritmusa de Bruijn szekvenciákat használ egy minimális tökéletes hash függvény létrehozásához, amely megszünteti az összes ágat. Ez az algoritmus feltételezi, hogy a szorzás eredménye 32 bitre van csonkolva.

for i from 0 to 31: table[ ( 0x077CB531 * ( 1 << i ) ) >> 27 ] ← i  // table [0..31] initialized
function ctz5 (x)
    return table[((x & -x) * 0x077CB531) >> 27]

Az (x & -x) kifejezés ismét izolálja a legkevésbé szignifikáns 1 bitet. Ekkor már csak 32 lehetséges szó van, amelyeket az előjel nélküli szorzás és eltolás hash a megfelelő helyre a táblázatban. (Ez az algoritmus nem kezeli a nulla bemenetet.)

CLZ

A kanonikus algoritmus egy-egy bitet vizsgál az MSB-től kezdve, amíg egy nullától eltérő bitet talál, amint ez a példában látható. O (n) időben fut, ahol n az operandus bithossza, és nem praktikus algoritmus általános használatra.

function clz1 (x)
    if x = 0 return w
    t ← 1 << (w - 1)
    r ← 0
    while (x & t) = 0
        t ← t >> 1
        r ← r + 1
    return r

Az előző ciklusos megközelítés javulása egyszerre nyolc bitet vizsgál, majd 256 (2 8 ) bejegyzési keresőtáblát használ az első nem nulla bájthoz. Ez a megközelítés azonban még mindig O (n) a végrehajtási időben.

function clz2 (x)
    if x = 0 return w
    t ← 0xff << (w - 8)
    r ← 0
    while (x & t) = 0
        t ← t >> 8
        r ← r + 8
    return r + table[x >> (w - 8 - r)]

A bináris keresés O -ra csökkentheti a végrehajtási időt (log 2 n):

function clz3 (x)
    if x = 0 return 32
    n ← 0
    if (x & 0xFFFF0000) = 0: n ← n + 16, x ← x << 16
    if (x & 0xFF000000) = 0: n ← n +  8, x ← x <<  8
    if (x & 0xF0000000) = 0: n ← n +  4, x ← x <<  4
    if (x & 0xC0000000) = 0: n ← n +  2, x ← x <<  2
    if (x & 0x80000000) = 0: n ← n +  1
    return n

A clz szimulációjának leggyorsabb hordozható módszerei a bináris keresés és a táblakeresés kombinációja: 8 bites táblakeresés (2 8 = 256 1 bájtos bejegyzés) helyettesítheti a bináris keresés alsó 3 ágát. A 64 bites operandusok további elágazást igényelnek. Nagyobb szélességű keresés is használható, de a maximális táblázat méretét korlátozza a modern processzorok L1 adatgyorsítótárának mérete, ami sokak számára 32 KB. Az ág mentését több mint ellensúlyozza az L1 gyorsítótár kihagyásának késése .

A CTZ-hez hasonló de Bruijn-szorzáshoz hasonló algoritmus működik a CLZ-nél, de ahelyett, hogy elkülönítené a legjelentősebb bitet, felfelé kerekít a 2 n −1 alak legközelebbi egész számához, eltolások és bites soros OR-ok használatával:

table[0..31] = {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
                8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}
function clz4 (x)
    for each y in {1, 2, 4, 8, 16}: x ← x | (x >> y)
    return table[((x * 0x07C4ACDD) >> 27) % 32]

A mély csővezetékekkel rendelkező processzorok, például a Prescott és az újabb Intel processzorok esetében gyorsabb lehet az ágak kicserélése bites bontású ÉS és VAGY operátorokra (annak ellenére, hogy sokkal több utasításra van szükség), hogy elkerüljék a csővezeték öblítését a rosszul előre jelzett ágaknál (és az ilyen típusú elágazások eredendően kiszámíthatatlan):

function clz5 (x)
   r = (x > 0xFFFF) << 4; x >>= r;
   q = (x > 0xFF  ) << 3; x >>= q; r |= q;
   q = (x > 0xF   ) << 2; x >>= q; r |= q;
   q = (x > 0x3   ) << 1; x >>= q; r |= q;
                                   r |= (x >> 1);
   return r;

Azon platformokon, amelyek az egész számok lebegőpontosra történő hardverkonvertálását biztosítják, a kitevő mező kibontható és kivonható egy konstansból a vezető nullák számának kiszámításához. A kerekítési hibák figyelembevételéhez javításokra van szükség. A lebegőpontos konverzió jelentős késéssel járhat. Ez a módszer nem hordozható, és általában nem ajánlott.

int x; 
int r;
union { unsigned int u[2]; double d; } t; 

t.u[LE] = 0x43300000;  // LE is 1 for little-endian
t.u[!LE] = x;
t.d -= 4503599627370496.0;
r = (t.u[LE] >> 20) - 0x3FF;  // log2
r++;  // CLZ

Alkalmazások

A számláló vezető nullák (clz) művelet használható a normalizálás hatékony végrehajtására , amely egész számot kódol m  × 2 e -ként , ahol m a legjelentősebb bitje ismert pozícióban (például a legmagasabb pozícióban). Ez viszont felhasználható Newton – Raphson felosztás megvalósítására , egész szám lebegőpontos konvertálására a szoftverekben és más alkalmazásokban.

A számoló első nullák (clz) segítségével kiszámítható a 32 bites "x = y" predikátum (nulla, ha igaz, egy hamis) a clz (x-y) >> 5 azonosságon keresztül , ahol a ">>" nincs aláírva jobb váltás. Kifinomultabb bitműveletek végrehajtására használható, mint például az n 1 bites első karakterlánc megkeresése . A clz (x-y) 1 << (16-clz (x-1)/2) kifejezés hatékony kezdeti tippelés egy 32 bites egész négyzetgyök kiszámításához Newton módszerével . ClZ hatékonyan végrehajtani null szuppresszió , gyors adattömörítés technika, amely kódol egy egész, mint a számos vezető nulla bájt együtt nem nulla bájt. Hatékonyan exponenciálisan eloszló egész számokat is generálhat , ha egyenletesen véletlenszerű egész számokat vesz fel .

A 2 naplóbázis felhasználható annak előrejelzésére, hogy a szorzás túlcsordul -e, mivel ⌈log 2 (xy) ⌉ ≤ ⌈log 2 (x) ⌉ + ⌈log 2 (y) ⌉ .

A számláló első nullákat és a számláló záró nullákat együtt lehet alkalmazni a Gosper hurokészlelési algoritmusának megvalósítására , amely korlátozott erőforrások felhasználásával képes megtalálni egy véges tartományú függvény időszakát.

A bináris GCD algoritmus sok ciklust tölt el a záró nullák eltávolításával; ezt helyettesíthetjük a számláló nullák végével (ctz), amelyet egy eltolás követ. Hasonló ciklus jelenik meg a jégeső szekvencia számításaiban .

Egy bittömb használható prioritási sor megvalósítására . Ebben az összefüggésben a Find first set (ffs) hasznos a "pop" vagy a "legmagasabb prioritású elem húzása" művelet hatékony végrehajtásához. A Linux kernel valós idejű ütemezője belsőleg ezt használja sched_find_first_bit().

A nullák számlálási művelete egyszerű és optimális megoldást kínál a Hanoi torony problémájára: a lemezek nulláról vannak számozva, és k lépésnél a ctz ( k ) lemezszámot a lehető legkisebb távolságra tolják jobbra (visszafelé körözve) szükség szerint hagyjuk). Azt is generál egy Gray-kód azáltal tetszőleges szót, és essek bit CTZ ( k ) lépésben k .

Lásd még

Megjegyzések

Hivatkozások

További irodalom

Külső linkek