it-swarm.com.de

Position des niedrigstwertigen Bits, das gesetzt wird

Ich suche nach einem effizienten Weg, um die Position des niedrigstwertigen Bits zu bestimmen, das in einer ganzen Zahl gesetzt ist, z. für 0x0FF0 wäre das 4. 

Eine triviale Implementierung ist folgende:

unsigned GetLowestBitPos(unsigned value)
{
   assert(value != 0); // handled separately

   unsigned pos = 0;
   while (!(value & 1))
   {
      value >>= 1;
      ++pos;
   }
   return pos;
}

Irgendwelche Ideen, wie man einige Zyklen daraus herauspressen kann?

(Anmerkung: Diese Frage ist für Leute, die solche Dinge mögen, nicht für Leute, die mir sagen, dass Xyzoptimierung böse ist.)

[edit] Danke an alle für die Ideen! Ich habe auch ein paar andere Dinge gelernt. Cool!

101
peterchen

Bit Twiddling Hacks bietet eine hervorragende Sammlung von äh, ein bisschen Twiddling Hacks, mit einer Diskussion über Leistung/Optimierung. Meine Lieblingslösung für Ihr Problem (von dieser Seite) ist «Multiplizieren und Nachschlagen»:

unsigned int v;  // find the number of trailing zeros in 32-bit v 
int r;           // result goes here
static const int MultiplyDeBruijnBitPosition[32] = 
{
  0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
  31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];

Hilfreiche Hinweise:

158
Anton Tykhyy

Warum nicht die eingebauten ffs verwenden? (Ich habe mir eine Manpage von Linux besorgt, die aber weitaus mehr verfügbar ist.)

ffs (3) - Linux-Manpage

Name

ffs - Finde das erste in einem Word gesetzte Bit

Zusammenfassung

#include <strings.h>
int ffs(int i);
#define _GNU_SOURCE
#include <string.h>
int ffsl(long int i);
int ffsll(long long int i);

Beschreibung

Die Funktion ffs () gibt die Position des ersten (niedrigstwertigen) Bits zurück, das im Wort i gesetzt ist. Das niedrigstwertige Bit ist Position 1 und die signifikanteste Position, z. 32 oder 64. Die Funktionen ffsll () und ffsl () machen dasselbe, nehmen jedoch Argumente von möglicherweise unterschiedlicher Größe an.

Rückgabewert

Diese Funktionen geben die Position des ersten gesetzten Bits zurück oder 0, wenn keine Bits in i gesetzt sind.

Entsprechend

4.3BSD, POSIX.1-2001.

Anmerkungen

BSD-Systeme haben einen Prototyp in <string.h>.

72
ephemient

Es gibt eine x86-Assembly-Anweisung (bsf), die dies ausführt. :)

Mehr optimiert ?!

Randnotiz:

Die Optimierung auf dieser Ebene ist naturgemäß von der Architektur abhängig. Heutige Prozessoren sind zu komplex (in Bezug auf die Verzweigungsvorhersage, Cache-Fehler, Pipelining), dass es so schwer ist vorherzusagen, welcher Code auf welcher Architektur schneller ausgeführt wird. Das Verringern von Operationen von 32 auf 9 oder ähnliches kann die Leistung einiger Architekturen sogar beeinträchtigen. Optimierter Code in einer einzelnen Architektur kann zu schlechterem Code in der anderen Architektur führen. Ich denke, Sie würden dies entweder für eine bestimmte CPU optimieren oder es so belassen und den Compiler entscheiden lassen, was es für besser hält.

45
Mehrdad Afshari

Die meisten modernen Architekturen verfügen über Anweisungen, um die Position des niedrigsten gesetzten Bits oder des höchsten gesetzten Bits zu ermitteln oder die Anzahl der führenden Nullen usw. zu zählen.

Wenn Sie eine Anweisung dieser Klasse haben, können Sie die anderen kostengünstig nachahmen.

Nehmen Sie sich einen Moment Zeit, um es auf Papier durchzuarbeiten, und stellen Sie fest, dass x & (x-1) das niedrigste gesetzte Bit in x löscht und ( x & ~(x-1) ) nur das niedrigste gesetzte Bit zurückgibt, unabhängig von Architektur, Wortlänge usw. Wenn Sie dies wissen, ist die Verwendung der Hardware-Zählung einfach -Leading-Nullen/höchst gesetztes Bit, um das niedrigste gesetzte Bit zu finden, wenn keine explizite Anweisung vorhanden ist.

Wenn überhaupt keine relevante Hardwareunterstützung vorhanden ist, kann die Multiplikations- und Nachschlageimplementierung von count-Leading-Nullen, die hier oder eine der auf der Bit Twiddling Hacks - Seite angegebenen, trivial in give umgewandelt werden niedrigstes gesetztes Bit unter Verwendung der obigen Identitäten und hat den Vorteil, dass er verzweigungslos ist.

36
moonshadow

Weee, jede Menge Lösungen und kein Benchmark in Sicht. Ihr Leute sollte sich schämen ;-)

Mein Computer ist ein Intel i530 (2,9 GHz), auf dem Windows 7 64-Bit ausgeführt wird. Ich habe mit einer 32-Bit-Version von MinGW kompiliert.

$ gcc --version
gcc.exe (GCC) 4.7.2

$ gcc bench.c -o bench.exe -std=c99 -Wall -O2
$ bench
Naive loop.         Time = 2.91  (Original questioner)
De Bruijn multiply. Time = 1.16  (Tykhyy)
Lookup table.       Time = 0.36  (Andrew Grant)
FFS instruction.    Time = 0.90  (ephemient)
Branch free mask.   Time = 3.48  (Dan / Jim Balter)
Double hack.        Time = 3.41  (DocMax)

$ gcc bench.c -o bench.exe -std=c99 -Wall -O2 -march=native
$ bench
Naive loop.         Time = 2.92
De Bruijn multiply. Time = 0.47
Lookup table.       Time = 0.35
FFS instruction.    Time = 0.68
Branch free mask.   Time = 3.49
Double hack.        Time = 0.92

Mein Code:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>


#define ARRAY_SIZE 65536
#define NUM_ITERS 5000  // Number of times to process array


int find_first_bits_naive_loop(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned value = nums[i];
            if (value == 0)
                continue;
            unsigned pos = 0;
            while (!(value & 1))
            {
                value >>= 1;
                ++pos;
            }
            total += pos + 1;
        }
    }

    return total;
}


int find_first_bits_de_bruijn(unsigned nums[ARRAY_SIZE])
{
    static const int MultiplyDeBruijnBitPosition[32] = 
    {
       1, 2, 29, 3, 30, 15, 25, 4, 31, 23, 21, 16, 26, 18, 5, 9, 
       32, 28, 14, 24, 22, 20, 17, 8, 27, 13, 19, 7, 12, 6, 11, 10
    };

    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned int c = nums[i];
            total += MultiplyDeBruijnBitPosition[((unsigned)((c & -c) * 0x077CB531U)) >> 27];
        }
    }

    return total;
}


unsigned char lowestBitTable[256];
int get_lowest_set_bit(unsigned num) {
    unsigned mask = 1;
    for (int cnt = 1; cnt <= 32; cnt++, mask <<= 1) {
        if (num & mask) {
            return cnt;
        }
    }

    return 0;
}
int find_first_bits_lookup_table(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned int value = nums[i];
            // note that order to check indices will depend whether you are on a big 
            // or little endian machine. This is for little-endian
            unsigned char *bytes = (unsigned char *)&value;
            if (bytes[0])
                total += lowestBitTable[bytes[0]];
            else if (bytes[1])
              total += lowestBitTable[bytes[1]] + 8;
            else if (bytes[2])
              total += lowestBitTable[bytes[2]] + 16;
            else
              total += lowestBitTable[bytes[3]] + 24;
        }
    }

    return total;
}


int find_first_bits_ffs_instruction(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            total +=  __builtin_ffs(nums[i]);
        }
    }

    return total;
}


int find_first_bits_branch_free_mask(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned value = nums[i];
            int i16 = !(value & 0xffff) << 4;
            value >>= i16;

            int i8 = !(value & 0xff) << 3;
            value >>= i8;

            int i4 = !(value & 0xf) << 2;
            value >>= i4;

            int i2 = !(value & 0x3) << 1;
            value >>= i2;

            int i1 = !(value & 0x1);

            int i0 = (value >> i1) & 1? 0 : -32;

            total += i16 + i8 + i4 + i2 + i1 + i0 + 1;
        }
    }

    return total;
}


int find_first_bits_double_hack(unsigned nums[ARRAY_SIZE])
{
    int total = 0; // Prevent compiler from optimizing out the code
    for (int j = 0; j < NUM_ITERS; j++) {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            unsigned value = nums[i];
            double d = value ^ (value - !!value); 
            total += (((int*)&d)[1]>>20)-1022; 
        }
    }

    return total;
}


int main() {
    unsigned nums[ARRAY_SIZE];
    for (int i = 0; i < ARRAY_SIZE; i++) {
        nums[i] = Rand() + (Rand() << 15);
    }

    for (int i = 0; i < 256; i++) {
        lowestBitTable[i] = get_lowest_set_bit(i);
    }


    clock_t start_time, end_time;
    int result;

    start_time = clock();
    result = find_first_bits_naive_loop(nums);
    end_time = clock();
    printf("Naive loop.         Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_de_bruijn(nums);
    end_time = clock();
    printf("De Bruijn multiply. Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_lookup_table(nums);
    end_time = clock();
    printf("Lookup table.       Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_ffs_instruction(nums);
    end_time = clock();
    printf("FFS instruction.    Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_branch_free_mask(nums);
    end_time = clock();
    printf("Branch free mask.   Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);

    start_time = clock();
    result = find_first_bits_double_hack(nums);
    end_time = clock();
    printf("Double hack.        Time = %.2f, result = %d\n", 
        (end_time - start_time) / (double)(CLOCKS_PER_SEC), result);
}
16

Die schnellste Lösung (nicht intrinsisch/nicht-Assembler) besteht darin, das niedrigste Byte zu finden und dieses Byte dann in einer Nachschlagetabelle mit 256 Einträgen zu verwenden. Dies bedeutet, dass Sie im schlechtesten Fall vier bedingte Anweisungen und im besten Fall 1 ausführen. Dies ist nicht nur die geringste Anzahl an Anweisungen, sondern auch die geringste Anzahl an Verzweigungen, die auf moderner Hardware überaus wichtig ist.

Ihre Tabelle (256 8-Bit-Einträge) sollte für jede Zahl im Bereich 0-255 den Index des LSB enthalten. Sie überprüfen jedes Byte Ihres Werts und finden das niedrigste Byte, das nicht Null ist, und verwenden diesen Wert, um den realen Index nachzuschlagen.

Dies erfordert 256 Bytes Speicher, aber wenn die Geschwindigkeit dieser Funktion so wichtig ist, lohnt es sich, 256 Bytes wert zu sein.

Z.B.

byte lowestBitTable[256] = {
.... // left as an exercise for the reader to generate
};

unsigned GetLowestBitPos(unsigned value)
{
  // note that order to check indices will depend whether you are on a big 
  // or little endian machine. This is for little-endian
  byte* bytes = (byte*)value;
  if (bytes[0])
    return lowestBitTable[bytes[0]];
  else if (bytes[1])
      return lowestBitTable[bytes[1]] + 8;
  else if (bytes[2])
      return lowestBitTable[bytes[2]] + 16;
  else
      return lowestBitTable[bytes[3]] + 24;  
}
16
Andrew Grant

OMG hat dies gerade gewunden.

Was den meisten dieser Beispiele fehlt, ist ein wenig Verständnis dafür, wie die gesamte Hardware funktioniert.

Wann immer Sie eine Verzweigung haben, muss die CPU erraten, welche Verzweigung genommen wird. Die Anweisungsleitung wird mit den Anweisungen geladen, die den erratenen Pfad nach unten führen. Wenn die CPU einen Fehler erraten hat, wird die Anweisungsleitung gelöscht und der andere Zweig muss geladen werden.

Betrachten Sie die einfache while-Schleife oben. Die Vermutung wird darin bestehen, innerhalb der Schleife zu bleiben. Es wird mindestens einmal falsch sein, wenn es die Schleife verlässt. Dies wird die Anweisungsleitung spülen. Dieses Verhalten ist etwas besser als die Annahme, dass die Schleife verlassen wird. In diesem Fall würde die Anweisungsleitung bei jeder Iteration geleert. 

Die Anzahl der verlorenen CPU-Zyklen variiert stark von einem Prozessortyp zum nächsten. Es können jedoch zwischen 20 und 150 verlorene CPU-Zyklen erwartet werden.

Die nächste, schlechtere Gruppe ist die, in der Sie denken, dass Sie ein paar Iterationen sparen möchten, indem Sie den Wert in kleinere Teile aufteilen und mehrere weitere Zweige hinzufügen. Jeder dieser Zweige bietet eine zusätzliche Möglichkeit zum Durchspülen der Anweisungsleitung und kostet weitere 20 bis 150 Taktzyklen.

Betrachten wir, was passiert, wenn Sie einen Wert in einer Tabelle nachschlagen. Wahrscheinlich befindet sich der Wert derzeit nicht im Cache, zumindest nicht beim ersten Aufruf Ihrer Funktion. Dies bedeutet, dass die CPU angehalten wird, während der Wert aus dem Cache geladen wird. Dies ist wiederum von Maschine zu Maschine unterschiedlich. Die neuen Intel-Chips nutzen dies tatsächlich als Gelegenheit, um Threads auszutauschen, während der aktuelle Thread darauf wartet, dass der Cache geladen wird. Dies kann leicht teurer sein als ein Befehlspuffer, doch wenn Sie diesen Vorgang mehrmals ausführen, ist es wahrscheinlich, dass er nur einmal auftritt.

Die schnellste Lösung mit konstanter Zeit ist eindeutig eine deterministische Mathematik. Eine reine und elegante Lösung.

Ich entschuldige mich, wenn dies bereits abgedeckt war.

Jeder Compiler, den ich verwende, außer XCODE AFAIK, verfügt über Compiler-Intrinsics sowohl für den Forward-Bitscan als auch für den Reverse-Bitscan. Diese werden auf der meisten Hardware zu einer einzigen Assembly-Anweisung kompiliert, ohne Cache-Miss, keine Branch-Miss-Prediction und kein anderer Programmierer, der Stolperblöcke erzeugt.

Für Microsoft-Compiler verwenden Sie _BitScanForward & _BitScanReverse.
Für GCC verwenden Sie __builtin_ffs, __builtin_clz, __builtin_ctz.

Bitte verzichten Sie außerdem darauf, eine Antwort zu posten und möglicherweise Neulinge irrezuführen, wenn Sie nicht ausreichend über das behandelte Thema Bescheid wissen.

Es tut mir leid, ich habe völlig vergessen, eine Lösung bereitzustellen. Dies ist der Code, den ich auf dem IPAD verwende, der keine Anweisung auf Assembly-Ebene für die Aufgabe hat: 

unsigned BitScanLow_BranchFree(unsigned value)
{
    bool bwl = (value & 0x0000ffff) == 0;
    unsigned I1 = (bwl * 15);
    value = (value >> I1) & 0x0000ffff;

    bool bbl = (value & 0x00ff00ff) == 0;
    unsigned I2 = (bbl * 7);
    value = (value >> I2) & 0x00ff00ff;

    bool bnl = (value & 0x0f0f0f0f) == 0;
    unsigned I3 = (bnl * 3);
    value = (value >> I3) & 0x0f0f0f0f;

    bool bsl = (value & 0x33333333) == 0;
    unsigned I4 = (bsl * 1);
    value = (value >> I4) & 0x33333333;

    unsigned result = value + I1 + I2 + I3 + I4 - 1;

    return result;
}

Hier ist zu verstehen, dass nicht der Vergleich teuer ist, sondern der Zweig, der nach dem Vergleich auftritt. In diesem Fall wird der Vergleich mit .. == 0 auf einen Wert von 0 oder 1 gesetzt, und das Ergebnis wird verwendet, um die Mathematik zu kombinieren, die auf beiden Seiten der Verzweigung aufgetreten wäre.

Bearbeiten:

Der obige Code ist total kaputt. Dieser Code funktioniert und ist nach wie vor verzweigt (falls optimiert):

int BitScanLow_BranchFree(ui value)
{
    int i16 = !(value & 0xffff) << 4;
    value >>= i16;

    int i8 = !(value & 0xff) << 3;
    value >>= i8;

    int i4 = !(value & 0xf) << 2;
    value >>= i4;

    int i2 = !(value & 0x3) << 1;
    value >>= i2;

    int i1 = !(value & 0x1);

    int i0 = (value >> i1) & 1? 0 : -32;

    return i16 + i8 + i4 + i2 + i1 + i0;
}

Wenn 0 angegeben wird, wird -1 zurückgegeben. Wenn Sie sich nicht für 0 interessieren oder 31 für 0 erhalten möchten, entfernen Sie die i0-Berechnung, um Zeit zu sparen.

11
Dan

Inspiriert von diesem ähnlichen Beitrag , bei dem nach einem Satzbit gesucht wird, biete ich Folgendes an:

unsigned GetLowestBitPos(unsigned value)
{
   double d = value ^ (value - !!value); 
   return (((int*)&d)[1]>>20)-1023; 
}

Pros:

  • keine Schleifen
  • keine Verzweigung
  • läuft in konstanter zeit
  • behandelt den Wert = 0, indem er ein Ergebnis außerhalb der Grenzen zurückgibt
  • nur zwei Zeilen Code

Nachteile:

  • geht davon aus, dass es sich um wenig endianness handelt (kann durch Ändern der Konstanten behoben werden)
  • geht davon aus, dass double ein echter * 8 IEEE-Float ist (IEEE 754)

Update: Wie in den Kommentaren hervorgehoben, ist eine Union eine sauberere Implementierung (zumindest für C) und würde wie folgt aussehen:

unsigned GetLowestBitPos(unsigned value)
{
    union {
        int i[2];
        double d;
    } temp = { .d = value ^ (value - !!value) };
    return (temp.i[1] >> 20) - 1023;
}

Dies setzt 32-Bit-Ints mit Little-Endian-Speicher für alles voraus (denken Sie an x86-Prozessoren).

7
DocMax

Es kann mit einem schlimmsten Fall von weniger als 32 Operationen durchgeführt werden:

Prinzip: Die Überprüfung auf 2 oder mehr Bits ist genauso effizient wie die Überprüfung auf 1 Bit. 

Es gibt zum Beispiel nichts, was Sie daran hindert, nach der Gruppierung zu suchen, die zuerst angezeigt wird, und dann jedes Bit vom kleinsten zum größten in dieser Gruppe zu prüfen.

So...
Wenn Sie jeweils 2 Bits prüfen, haben Sie im ungünstigsten Fall (NBits/2) + 1 die Gesamtzahl.
Wenn Sie jeweils 3 Bits prüfen, haben Sie im ungünstigsten Fall (NBits/3) + 2 Gesamtprüfungen.
...

Optimal wäre das Einchecken von 4er-Gruppen, für die im schlimmsten Fall 11 Operationen statt Ihrer 32 erforderlich wären. 

Der beste Fall geht von der Prüfung 1 Ihrer Algorithmen bis zu 2 Prüfungen, ob Sie diese Gruppierungsidee verwenden. Aber dieser zusätzliche 1-Check im besten Fall lohnt sich für die schlimmsten Fälle. 

Hinweis: Ich schreibe es komplett aus, anstatt eine Schleife zu verwenden, weil es auf diese Weise effizienter ist.  

int getLowestBitPos(unsigned int value)
{
    //Group 1: Bits 0-3
    if(value&0xf)
    {
        if(value&0x1)
            return 0;
        else if(value&0x2)
            return 1;
        else if(value&0x4)
            return 2;
        else
            return 3;
    }

    //Group 2: Bits 4-7
    if(value&0xf0)
    {
        if(value&0x10)
            return 4;
        else if(value&0x20)
            return 5;
        else if(value&0x40)
            return 6;
        else
            return 7;
    }

    //Group 3: Bits 8-11
    if(value&0xf00)
    {
        if(value&0x100)
            return 8;
        else if(value&0x200)
            return 9;
        else if(value&0x400)
            return 10;
        else
            return 11;
    }

    //Group 4: Bits 12-15
    if(value&0xf000)
    {
        if(value&0x1000)
            return 12;
        else if(value&0x2000)
            return 13;
        else if(value&0x4000)
            return 14;
        else
            return 15;
    }

    //Group 5: Bits 16-19
    if(value&0xf0000)
    {
        if(value&0x10000)
            return 16;
        else if(value&0x20000)
            return 17;
        else if(value&0x40000)
            return 18;
        else
            return 19;
    }

    //Group 6: Bits 20-23
    if(value&0xf00000)
    {
        if(value&0x100000)
            return 20;
        else if(value&0x200000)
            return 21;
        else if(value&0x400000)
            return 22;
        else
            return 23;
    }

    //Group 7: Bits 24-27
    if(value&0xf000000)
    {
        if(value&0x1000000)
            return 24;
        else if(value&0x2000000)
            return 25;
        else if(value&0x4000000)
            return 26;
        else
            return 27;
    }

    //Group 8: Bits 28-31
    if(value&0xf0000000)
    {
        if(value&0x10000000)
            return 28;
        else if(value&0x20000000)
            return 29;
        else if(value&0x40000000)
            return 30;
        else
            return 31;
    }

    return -1;
}
4
Brian R. Bondy

Warum nicht binäre Suche verwenden? Dies wird immer nach 5 Operationen abgeschlossen (unter der Annahme einer Int-Größe von 4 Byte):

if (0x0000FFFF & value) {
    if (0x000000FF & value) {
        if (0x0000000F & value) {
            if (0x00000003 & value) {
                if (0x00000001 & value) {
                    return 1;
                } else {
                    return 2;
                }
            } else {
                if (0x0000004 & value) {
                    return 3;
                } else {
                    return 4;
                }
            }
        } else { ...
    } else { ...
} else { ...
4
soulmerge

Nach der Chess Programming BitScan-Seite und meinen eigenen Messungen, subtrahieren und xor ist schneller als negieren und maskieren.

(Beachten Sie, als wenn Sie die nachgestellten Nullen in 0 zählen werden, die Methode, wie ich sie habe, gibt 63 zurück, während negate und mask 0 zurückgeben.)

Hier ist ein 64-Bit-Subtrahieren und Xor:

unsigned long v;  // find the number of trailing zeros in 64-bit v 
int r;            // result goes here
static const int MultiplyDeBruijnBitPosition[64] = 
{
  0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61,
  54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11, 4, 62,
  46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45,
  25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v ^ (v-1)) * 0x03F79D71B4CB0A89U)) >> 58];

Hier sehen Sie eine 64-Bit-Version der Negate- und Mask-Methode:

unsigned long v;  // find the number of trailing zeros in 64-bit v 
int r;            // result goes here
static const int MultiplyDeBruijnBitPosition[64] = 
{
  0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4,
  62, 55, 59, 36, 53, 51, 43, 22, 45, 39, 33, 30, 24, 18, 12, 5,
  63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11,
  46, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x03F79D71B4CB0A89U)) >> 58];
2
jnm2

Eine andere Methode (Modulus Division und Lookup) verdient hier eine besondere Erwähnung aus demselben link , das von @ anton-tykhyy bereitgestellt wird. Diese Methode ist in der Leistung der DeBruijn-Multiplikations- und Nachschlagemethode sehr ähnlich, mit einem geringfügigen, aber wichtigen Unterschied.

Modulteilung und Nachschlagen

 unsigned int v;  // find the number of trailing zeros in v
    int r;           // put the result in r
    static const int Mod37BitPosition[] = // map a bit value mod 37 to its position
    {
      32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4,
      7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5,
      20, 8, 19, 18
    };
    r = Mod37BitPosition[(-v & v) % 37];

die Modulus Division und die Lookup-Methode geben verschiedene Werte für v = 0x00000000 und v = FFFFFFFF zurück, während die DeBruijn-Multiplikations- und Lookup-Methode an beiden Eingängen Null zurückgibt.

prüfung:-

unsigned int n1=0x00000000, n2=0xFFFFFFFF;

MultiplyDeBruijnBitPosition[((unsigned int )((n1 & -n1) * 0x077CB531U)) >> 27]); /* returns 0 */
MultiplyDeBruijnBitPosition[((unsigned int )((n2 & -n2) * 0x077CB531U)) >> 27]); /* returns 0 */
Mod37BitPosition[(((-(n1) & (n1))) % 37)]); /* returns 32 */
Mod37BitPosition[(((-(n2) & (n2))) % 37)]); /* returns 0 */
2
RaviSharma

In meiner Antwort hier erfahren Sie, wie Sie dies mit einer einzelnen x86-Anweisung tun können, mit der Ausnahme, dass Sie zum Ermitteln des least - Bits mit dem niedrigsten Satz die BSF-Anweisung ("bit scan forward") anstelle von BSR verwenden möchten Dort. 

1
timday

Eine andere Lösung, möglicherweise nicht die schnellste, scheint aber recht gut zu sein.
Zumindest hat es keine Niederlassungen. ;)

uint32 x = ...;  // 0x00000001  0x0405a0c0  0x00602000
x |= x <<  1;    // 0x00000003  0x0c0fe1c0  0x00e06000
x |= x <<  2;    // 0x0000000f  0x3c3fe7c0  0x03e1e000
x |= x <<  4;    // 0x000000ff  0xffffffc0  0x3fffe000
x |= x <<  8;    // 0x0000ffff  0xffffffc0  0xffffe000
x |= x << 16;    // 0xffffffff  0xffffffc0  0xffffe000

// now x is filled with '1' from the least significant '1' to bit 31

x = ~x;          // 0x00000000  0x0000003f  0x00001fff

// now we have 1's below the original least significant 1
// let's count them

x = x & 0x55555555 + (x >>  1) & 0x55555555;
                 // 0x00000000  0x0000002a  0x00001aaa

x = x & 0x33333333 + (x >>  2) & 0x33333333;
                 // 0x00000000  0x00000024  0x00001444

x = x & 0x0f0f0f0f + (x >>  4) & 0x0f0f0f0f;
                 // 0x00000000  0x00000006  0x00000508

x = x & 0x00ff00ff + (x >>  8) & 0x00ff00ff;
                 // 0x00000000  0x00000006  0x0000000d

x = x & 0x0000ffff + (x >> 16) & 0x0000ffff;
                 // 0x00000000  0x00000006  0x0000000d
// least sign.bit pos. was:  0           6          13
1
CiaPan

Sie können prüfen, ob eines der niederwertigen Bits gesetzt ist. Wenn ja, dann schauen Sie sich die niedrigere Ordnung der restlichen Bits an. z.B.,:

32bit int - Prüfen Sie, ob eine der ersten 16 gesetzt ist ..__ Wenn dies der Fall ist, prüfen Sie, ob eine der ersten 8 gesetzt ist.

wenn nicht, prüfen Sie, ob eine der oberen 16 gesetzt ist.

Im Wesentlichen ist es die binäre Suche.

1
Arnshea
unsigned GetLowestBitPos(unsigned value)
{
    if (value & 1) return 1;
    if (value & 2) return 2;
    if (value & 4) return 3;
    if (value & 8) return 4;
    if (value & 16) return 5;
    if (value & 32) return 6;
    if (value & 64) return 7;
    if (value & 128) return 8;
    if (value & 256) return 9;
    if (value & 512) return 10;
    if (value & 1024) return 11;
    if (value & 2048) return 12;
    if (value & 4096) return 13;
    if (value & 8192) return 14;
    if (value & 16384) return 15;
    if (value & 32768) return 16;
    if (value & 65536) return 17;
    if (value & 131072) return 18;
    if (value & 262144) return 19;
    if (value & 524288) return 20;
    if (value & 1048576) return 21;
    if (value & 2097152) return 22;
    if (value & 4194304) return 23;
    if (value & 8388608) return 24;
    if (value & 16777216) return 25;
    if (value & 33554432) return 26;
    if (value & 67108864) return 27;
    if (value & 134217728) return 28;
    if (value & 268435456) return 29;
    if (value & 536870912) return 30;
    return 31;
}

50% aller Zahlen werden in der ersten Codezeile zurückgegeben.

75% aller Zahlen werden in den ersten beiden Codezeilen zurückgegeben.

87% aller Zahlen werden in den ersten 3 Codezeilen zurückgegeben.

94% aller Zahlen werden in den ersten 4 Codezeilen zurückgegeben.

97% aller Zahlen werden in den ersten 5 Codezeilen zurückgegeben.

usw.

Ich denke, Leute, die sich darüber beschweren, wie ineffizient das Worst-Case-Szenario für diesen Code ist, verstehen nicht, wie selten dieser Zustand eintreten wird.

1
BoltBait

Fand diesen intelligenten Trick mit "Zaubermasken" in "Die Kunst des Programmierens, Teil 4", der es in O(log(n)) Zeit für die n-Bit-Nummer macht. [mit Log (n) zusätzlichen Platz]. Typische Lösungen für das gesetzte Bit sind entweder O(n) oder benötigen O(n) zusätzlichen Platz für eine Nachschlagetabelle. Dies ist also ein guter Kompromiss.

Magische Masken:

m0 = (...............01010101)  
m1 = (...............00110011)
m2 = (...............00001111)  
m3 = (.......0000000011111111)
....

Schlüsselidee: Anzahl nachstehender Nullen in x = 1 * [(x & m0) = 0] + 2 * [(x & m1) = 0] + 4 * [(x & m2) = 0] + ...

int lastSetBitPos(const uint64_t x) {
    if (x == 0)  return -1;

    //For 64 bit number, log2(64)-1, ie; 5 masks needed
    int steps = log2(sizeof(x) * 8); assert(steps == 6);
    //magic masks
    uint64_t m[] = { 0x5555555555555555, //     .... 010101
                     0x3333333333333333, //     .....110011
                     0x0f0f0f0f0f0f0f0f, //     ...00001111
                     0x00ff00ff00ff00ff, //0000000011111111 
                     0x0000ffff0000ffff, 
                     0x00000000ffffffff };

    //Firstly extract only the last set bit
    uint64_t y = x & -x;

    int trailZeros = 0, i = 0 , factor = 0;
    while (i < steps) {
        factor = ((y & m[i]) == 0 ) ? 1 : 0;
        trailZeros += factor * pow(2,i);
        ++i;
    }
    return (trailZeros+1);
}
1
jayadev

Wenn C++ 11 für Sie verfügbar ist, kann ein Compiler die Aufgabe manchmal für Sie erledigen :)

constexpr std::uint64_t lssb(const std::uint64_t value)
{
    return !value ? 0 : (value % 2 ? 1 : lssb(value >> 1) + 1);
}

Ergebnis ist 1-basierter Index.

1
Ruslan Garipov

Hier ist eine einfache Alternative, auch wenn das Auffinden von Protokollen etwas kostspielig ist.

if(n == 0)
  return 0;
return log2(n & -n)+1;   //Assuming the bit index starts from 1
0
Siva Prakash

Dies betrifft die Antwort von @Anton Tykhyy

Hier ist meine C++ 11-Implementierung von constexpr, die Casts beseitigt und eine Warnung für VC++ 17 entfernt, indem ein 64-Bit-Ergebnis auf 32 Bit abgeschnitten wird:

constexpr uint32_t DeBruijnSequence[32] =
{
    0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
    31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
constexpr uint32_t ffs ( uint32_t value )
{
    return  DeBruijnSequence[ 
        (( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF)
            >> 27];
}

Um das Problem 0x1 und 0x0 zu umgehen, die beide 0 zurückgeben, können Sie Folgendes tun:

constexpr uint32_t ffs ( uint32_t value )
{
    return (!value) ? 32 : DeBruijnSequence[ 
        (( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF)
            >> 27];
}

wenn der Compiler den Aufruf jedoch nicht vorverarbeiten kann oder kann, werden der Berechnung einige Zyklen hinzugefügt.

Wenn Sie interessiert sind, finden Sie hier eine Liste statischer Asserts, um zu überprüfen, ob der Code das tut, was beabsichtigt ist:

static_assert (ffs(0x1) == 0, "Find First Bit Set Failure.");
static_assert (ffs(0x2) == 1, "Find First Bit Set Failure.");
static_assert (ffs(0x4) == 2, "Find First Bit Set Failure.");
static_assert (ffs(0x8) == 3, "Find First Bit Set Failure.");
static_assert (ffs(0x10) == 4, "Find First Bit Set Failure.");
static_assert (ffs(0x20) == 5, "Find First Bit Set Failure.");
static_assert (ffs(0x40) == 6, "Find First Bit Set Failure.");
static_assert (ffs(0x80) == 7, "Find First Bit Set Failure.");
static_assert (ffs(0x100) == 8, "Find First Bit Set Failure.");
static_assert (ffs(0x200) == 9, "Find First Bit Set Failure.");
static_assert (ffs(0x400) == 10, "Find First Bit Set Failure.");
static_assert (ffs(0x800) == 11, "Find First Bit Set Failure.");
static_assert (ffs(0x1000) == 12, "Find First Bit Set Failure.");
static_assert (ffs(0x2000) == 13, "Find First Bit Set Failure.");
static_assert (ffs(0x4000) == 14, "Find First Bit Set Failure.");
static_assert (ffs(0x8000) == 15, "Find First Bit Set Failure.");
static_assert (ffs(0x10000) == 16, "Find First Bit Set Failure.");
static_assert (ffs(0x20000) == 17, "Find First Bit Set Failure.");
static_assert (ffs(0x40000) == 18, "Find First Bit Set Failure.");
static_assert (ffs(0x80000) == 19, "Find First Bit Set Failure.");
static_assert (ffs(0x100000) == 20, "Find First Bit Set Failure.");
static_assert (ffs(0x200000) == 21, "Find First Bit Set Failure.");
static_assert (ffs(0x400000) == 22, "Find First Bit Set Failure.");
static_assert (ffs(0x800000) == 23, "Find First Bit Set Failure.");
static_assert (ffs(0x1000000) == 24, "Find First Bit Set Failure.");
static_assert (ffs(0x2000000) == 25, "Find First Bit Set Failure.");
static_assert (ffs(0x4000000) == 26, "Find First Bit Set Failure.");
static_assert (ffs(0x8000000) == 27, "Find First Bit Set Failure.");
static_assert (ffs(0x10000000) == 28, "Find First Bit Set Failure.");
static_assert (ffs(0x20000000) == 29, "Find First Bit Set Failure.");
static_assert (ffs(0x40000000) == 30, "Find First Bit Set Failure.");
static_assert (ffs(0x80000000) == 31, "Find First Bit Set Failure.");
0