Position des niedrigstwertigen Bits, das gesetzt ist


118

Ich suche nach einem effizienten Weg, um die Position des niedrigstwertigen Bits zu bestimmen, das in einer ganzen Zahl gesetzt ist, z. B. für 0x0FF0 wäre es 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 ein paar Zyklen herausquetscht?

(Hinweis: Diese Frage richtet sich an Personen, die solche Dinge mögen, und nicht an Personen, die mir sagen, dass Xyzoptimierung böse ist.)

[Bearbeiten] Vielen Dank an alle für die Ideen! Ich habe noch ein paar andere Dinge gelernt. Cool!


while ((Wert _N >> (++ pos))! = 0);
Thomas

Antworten:


169

Bit Twiddling Hacks bietet eine exzellente Sammlung von Bit Twiddling Hacks mit beigefügter Diskussion zu Leistung und Optimierung. Meine Lieblingslösung für Ihr Problem (von dieser Site) 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 Referenzen:


18
Warum das Downvote? Dies ist möglicherweise die schnellste Implementierung, abhängig von der Geschwindigkeit der Multiplikation. Es ist sicherlich Code Compact, und der (v & -v) Trick sollte jeder lernen und sich daran erinnern.
Adam Davis

2
+1 sehr cool, wie teuer ist eine Multiplikationsoperation im Vergleich zu einer if (X & Y) -Operation?
Brian R. Bondy

4
Weiß jemand, wie die Leistung im Vergleich zum __builtin_ffsloder ist ffsl?
Steven Lu

2
@ Jim Balter, aber Modulo ist sehr langsam im Vergleich zur Multiplikation auf moderner Hardware. Ich würde es also nicht als bessere Lösung bezeichnen.
Apriori

2
Es scheint mir, dass sowohl der Wert 0x01 als auch der Wert 0x00 den Wert 0 aus dem Array ergeben. Anscheinend zeigt dieser Trick an, dass das niedrigste Bit gesetzt ist, wenn 0 übergeben wird!
Abelenky

80

Warum nicht das eingebaute ffs verwenden ? (Ich habe mir eine Manpage von Linux geholt, aber sie ist weiter verbreitet.)

ffs (3) - Linux-Manpage

Name

ffs - Finde das erste in einem Wort 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 höchstwertige Position, z. B. 32 oder 64. Die Funktionen ffsll () und ffsl () machen dasselbe, nehmen jedoch Argumente mit möglicherweise unterschiedlicher Größe an.

Rückgabewert

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

Entsprechend

4.3BSD, POSIX.1-2001.

Anmerkungen

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


6
Zu Ihrer Information, dies wird mit dem entsprechenden Assembly-Befehl kompiliert, sofern verfügbar.
Jérémie

46

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

Optimierter?!

Randnotiz:

Die Optimierung auf dieser Ebene ist von Natur aus architekturabhängig. Die heutigen Prozessoren sind zu komplex (in Bezug auf Verzweigungsvorhersage, Cache-Fehler, Pipelining), als dass es so schwierig ist, vorherzusagen, welcher Code auf welcher Architektur schneller ausgeführt wird. Das Verringern von Vorgängen von 32 auf 9 oder ähnliches kann bei einigen Architekturen sogar die Leistung verringern. Optimierter Code auf einer einzelnen Architektur kann zu schlechterem Code auf der anderen führen. Ich denke, Sie würden dies entweder für eine bestimmte CPU optimieren oder es so lassen, wie es ist, und den Compiler entscheiden lassen, was er für besser hält.


20
@dwc: Ich verstehe, aber ich denke diese Klausel: "Irgendwelche Ideen, wie man einige Zyklen herausquetscht?" macht eine solche Antwort durchaus akzeptabel!
Mehrdad Afshari

5
+1 Seine Antwort hängt aufgrund der Endianness notwendigerweise von seiner Architektur ab, daher ist es eine absolut gültige Antwort, auf die Montageanleitung zurückzugreifen.
Chris Lutz

3
+1 Kluge Antwort, ja, es ist nicht C oder C ++, aber es ist das richtige Werkzeug für den Job.
Andrew Hare

1
Warten Sie, egal. Der tatsächliche Wert der Ganzzahl spielt hier keine Rolle. Es tut uns leid.
Chris Lutz

2
@ Bastian: Sie setzen ZF = 1, wenn der Operand Null ist.
Mehrdad Afshari

42

Die meisten modernen Architekturen verfügen über Anweisungen zum Ermitteln der Position des niedrigsten gesetzten Bits oder des höchsten gesetzten Bits oder zum Zählen der Anzahl führender Nullen usw.

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

Nehmen Sie sich einen Moment Zeit, um es auf Papier durchzuarbeiten und festzustellen, dass x & (x-1)das niedrigste gesetzte Bit in x ( x & ~(x-1) )gelöscht wird und nur das niedrigste gesetzte Bit zurückgegeben wird, unabhängig von Architektur, Wortlänge usw. Wenn Sie dies wissen, ist es trivial, die Hardware-Zählung zu verwenden -zeroes / höchstes gesetztes Bit, um das niedrigste gesetzte Bit zu finden, wenn keine explizite Anweisung dazu vorhanden ist.

Wenn überhaupt keine relevante Hardwareunterstützung vorhanden ist, wird die Multiplikations- und Suchimplementierung von Zähl-führenden Nullen angegeben hier oder einer von denen auf der Bit Twiddling Hacks Seite kann trivialerweise zu geben niedrigsten Satz umgewandelt werden Bit die obigen Identitäten und hat den Vorteil, verzweigt zu sein.


18

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

Mein Computer ist ein Intel i530 (2,9 GHz) mit Windows 7 64-Bit. 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);
}

8
Die Benchmarks für de Bruijn und Lookup können irreführend sein. Wenn Sie in einer engen Schleife wie dieser sitzen, werden die Nachschlagetabellen für jeden Typ nach der ersten Operation bis nach der letzten Schleife im L1-Cache fixiert. Dies entspricht wahrscheinlich nicht der tatsächlichen Nutzung.
MattW

1
Für die Eingänge mit einer Null im niedrigen Byte werden die höheren Bytes durch Speichern / Neuladen anstelle von Verschieben aufgrund der Zeigerumwandlung erhalten. (Übrigens völlig unnötig und macht es endianabhängig, anders als es eine Schicht nicht tun würde). Wie auch immer, das Mikrobenchmark ist nicht nur wegen des heißen Caches unrealistisch, es hat auch die Verzweigungsprädiktoren vorbereitet und testet Eingaben, die sehr gut vorhersagen und die LUT weniger Arbeit machen lassen. Viele reale Anwendungsfälle haben eine gleichmäßigere Verteilung der Ergebnisse, keine Eingaben.
Peter Cordes

2
Ihre FFS-Schleife wird leider durch eine falsche Abhängigkeit in der BSF-Anweisung verlangsamt, die Ihr verkrusteter alter Compiler nicht vermeidet ( aber neuere gcc sollte, ebenso wie popcnt / lzcnt / tzcnt ., BSFEine falsche Abhängigkeit von ihrer Ausgabe haben (seit dem tatsächlichen Verhalten) Wenn input = 0 die Ausgabe unverändert lassen soll, verwandelt gcc dies leider in eine schleifenübertragene Abhängigkeit, indem das Register zwischen den Schleifeniterationen nicht gelöscht wird. Daher sollte die Schleife mit einem von 5 Zyklen ausgeführt werden, was einen Engpass bei BSF (3) + CMOV darstellt (2) Latenz
Peter Cordes

1
Ihr Benchmark hat festgestellt, dass die LUT fast genau den doppelten Durchsatz der FFS-Methode hat, was meiner Vorhersage der statischen Analyse sehr gut entspricht :). Beachten Sie, dass Sie den Durchsatz und nicht die Latenz messen, da die einzige serielle Abhängigkeit in Ihrer Schleife die Summe ist. Ohne die falsche Abhängigkeit ffs()sollte ein Durchsatz von einem pro Takt vorliegen (3 Uops, 1 für BSF und 2 für CMOV, und sie können auf verschiedenen Ports ausgeführt werden). Mit dem gleichen Loop-Overhead können 7 ALU-Uops (auf Ihrer CPU) mit 3 pro Takt ausgeführt werden. Overhead dominiert! Quelle: agner.org/optimize
Peter Cordes

1
Ja, eine Ausführung außerhalb der Reihenfolge kann mehrere Iterationen der Schleife überlappen, wenn bsf ecx, [ebx+edx*4]sie nicht ecxals Eingabe behandelt wird, auf die gewartet werden muss. (ECX wurde zuletzt von der CMOV des vorherigen Iteratons geschrieben). Die CPU verhält sich jedoch so, um das Verhalten "Ziel unverändert lassen, wenn Quelle Null ist" zu implementieren (es handelt sich also nicht wirklich um eine falsche Abhängigkeit wie bei TZCNT; eine Datenabhängigkeit ist erforderlich, da unter der Annahme keine Verzweigung + spekulative Ausführung erfolgt dass der Eingang nicht Null ist). Wir könnten es überwinden, indem wir ein xor ecx,ecxvor dem hinzufügen bsf, um die Abhängigkeit von ECX zu brechen.
Peter Cordes

17

Die schnellste (nicht intrinsische / nicht Assembler-) Lösung besteht darin, das niedrigste Byte zu finden und dieses Byte dann in einer Nachschlagetabelle mit 256 Einträgen zu verwenden. Dies gibt Ihnen eine Worst-Case-Leistung von vier bedingten Anweisungen und eine Best-Case-Leistung von 1. Dies ist nicht nur die geringste Anzahl von Anweisungen, sondern auch die geringste Anzahl von Verzweigungen, was bei moderner Hardware überaus wichtig ist.

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

Dies erfordert 256 Byte Speicher, aber wenn die Geschwindigkeit dieser Funktion so wichtig ist, lohnt es sich, 256 Byte zu verwenden.

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;  
}

1
Es ist tatsächlich ein Worst-Case von drei Bedingungen :) Aber ja, dies ist der schnellste Ansatz (und normalerweise das, wonach die Leute in solchen Interviewfragen suchen).
Brian

4
Willst du nicht irgendwo +8, +16, +24?
Mark Ransom

7
Jede Nachschlagetabelle erhöht die Wahrscheinlichkeit eines Cache-Fehlschlags und kann die Kosten für den Speicherzugriff verursachen, die mehrere Größenordnungen höher sein können als die Ausführung von Anweisungen.
Mehrdad Afshari

1
Ich würde sogar Bit-Shifts verwenden (jedes Mal um 8 verschieben). könnte dann vollständig unter Verwendung von Registern erfolgen. Mit Zeigern müssen Sie auf den Speicher zugreifen.
Johannes Schaub - litb

1
Angemessene Lösung, aber zwischen dem Potenzial, dass sich die Nachschlagetabelle nicht im Cache befindet (was, wie bereits erwähnt, gelöst werden kann) und der Anzahl der Verzweigungen (mögliche Fehlvorhersage der Verzweigungen), bevorzuge ich die Lösung zum Multiplizieren und Nachschlagen (keine Verzweigungen, kleinere Nachschlagetabelle). Wenn Sie Intrinsics oder Inline-Assembly verwenden können, sind diese wahrscheinlich die bessere Wahl. Trotzdem ist diese Lösung nicht schlecht.

13

OMG hat dies gerade gewunden.

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

Jedes Mal, wenn Sie einen Zweig haben, muss die CPU erraten, welcher Zweig verwendet wird. Die Anweisungspipe wird mit den Anweisungen geladen, die den erratenen Pfad hinunterführen. Wenn die CPU falsch geraten hat, wird die Anweisungspipe geleert und der andere Zweig muss geladen werden.

Betrachten Sie die einfache while-Schleife oben. Die Vermutung wird sein, innerhalb der Schleife zu bleiben. Es wird mindestens einmal falsch sein, wenn es die Schleife verlässt. Dadurch wird die Anweisungsleitung gespült. Dieses Verhalten ist etwas besser als die Vermutung, dass es die Schleife verlässt. In diesem Fall würde es die Anweisungspipe bei jeder Iteration leeren.

Die Anzahl der CPU-Zyklen, die verloren gehen, variiert stark von einem Prozessortyp zum nächsten. Sie können jedoch mit 20 bis 150 verlorenen CPU-Zyklen rechnen.

In der nächst schlechteren Gruppe denken Sie, dass Sie einige Iterationen sparen werden, indem Sie den Wert in kleinere Teile aufteilen und mehrere weitere Zweige hinzufügen. Jeder dieser Zweige bietet eine zusätzliche Möglichkeit, die Anweisungsleitung zu spülen, und kostet weitere 20 bis 150 Taktzyklen.

Betrachten wir, was passiert, wenn Sie einen Wert in einer Tabelle nachschlagen. Möglicherweise befindet sich der Wert derzeit nicht im Cache, zumindest nicht beim ersten Aufruf Ihrer Funktion. Dies bedeutet, dass die CPU blockiert wird, während der Wert aus dem Cache geladen wird. Auch dies variiert von Maschine zu Maschine. Die neuen Intel-Chips nutzen dies tatsächlich als Gelegenheit, Threads auszutauschen, während der aktuelle Thread auf den Abschluss des Cache-Ladevorgangs wartet. Dies kann leicht teurer sein als eine Spülung der Anweisungsleitung. Wenn Sie diesen Vorgang jedoch mehrmals ausführen, tritt er wahrscheinlich nur einmal auf.

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

Ich entschuldige mich, wenn dies bereits behandelt wurde.

Jeder von mir verwendete Compiler mit Ausnahme von XCODE AFAIK verfügt über Compiler-Eigenschaften sowohl für den Vorwärts-Bitscan als auch für den Rückwärts-Bitscan. Diese werden auf den meisten Hardwarekomponenten ohne Cache-Miss, ohne Branch-Miss-Vorhersage und ohne andere vom Programmierer generierte Stolpersteine ​​zu einer einzigen Assembly-Anweisung kompiliert.

Verwenden Sie für Microsoft-Compiler _BitScanForward & _BitScanReverse.
Verwenden Sie für GCC __builtin_ffs, __builtin_clz, __builtin_ctz.

Bitte unterlassen Sie es außerdem, eine Antwort zu veröffentlichen und möglicherweise Neulinge irrezuführen, wenn Sie nicht ausreichend über das besprochene Thema informiert sind.

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

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. Der Vergleich wird in diesem Fall auf einen Wert von 0 oder 1 mit dem Wert .. == 0 gezwungen, und das Ergebnis wird verwendet, um die Mathematik zu kombinieren, die auf beiden Seiten des Zweigs aufgetreten wäre.

Bearbeiten:

Der obige Code ist völlig kaputt. Dieser Code funktioniert und ist immer noch verzweigungsfrei (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;
}

Dies gibt -1 zurück, wenn 0 angegeben wird. Wenn Sie sich nicht für 0 interessieren oder gerne 31 für 0 erhalten, entfernen Sie die i0-Berechnung, um Zeit zu sparen.


3
Ich habe es für dich repariert. Testen Sie unbedingt, was Sie posten.
Jim Balter

5
Wie kann man es "verzweigungsfrei" nennen, wenn es einen ternären Operator enthält?
BoltBait

2
Es ist eine bedingte Bewegung. Eine einzelne Assembler-Anweisung, die beide möglichen Werte als Parameter verwendet und eine Bewegungsoperation basierend auf der Auswertung der Bedingung ausführt. Und damit ist "Branch Free". Es erfolgt kein Sprung zu einer anderen unbekannten oder möglicherweise falschen Adresse.
Dan

FWIW gcc generiert Zweige sogar auf -O3 godbolt.org/z/gcsUHd
Qix - MONICA WURDE AM

7

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

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

Vorteile:

  • keine Schleifen
  • keine Verzweigung
  • läuft in konstanter Zeit
  • Behandelt den Wert = 0, indem ein ansonsten außerhalb der Grenzen liegendes Ergebnis zurückgegeben wird
  • nur zwei Codezeilen

Nachteile:

  • setzt wenig Endianness als codiert voraus (kann durch Ändern der Konstanten behoben werden)
  • geht davon aus, dass double ein realer * 8 IEEE-Float ist (IEEE 754)

Update: Wie in den Kommentaren erwähnt, ist eine Gewerkschaft eine sauberere Implementierung (zumindest für C) und würde folgendermaßen 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).


1
Interessant - ich habe immer noch Angst, Doppel für Bit-Arithmetik zu verwenden, aber ich werde es mir
merken

Die Verwendung von frexp () könnte es etwas portabler machen
aka.nice

1
Typ-Punning durch Zeiger-Casting ist in C oder C ++ nicht sicher. Verwenden Sie memcpy in C ++ oder eine Union in C. (Oder eine Union in C ++, wenn Ihr Compiler die Sicherheit garantiert. Beispielsweise garantieren die GNU-Erweiterungen für C ++ (die von vielen Compilern unterstützt werden), dass das Punnen von Unionstypen sicher ist.)
Peter Cordes

1
Älteres gcc macht auch besseren Code mit einer Union anstelle eines Zeiger-Cast: Es bewegt sich direkt von einem FP-Register (xmm0) zu rax (mit movq), anstatt zu speichern / neu zu laden. Neuere gcc und clang verwenden movq für beide Richtungen. Eine God- Version finden Sie unter godbolt.org/g/x7JBiL . Ist es beabsichtigt, dass Sie eine arithmetische Verschiebung um 20 machen? Ihre Annahmen sollten auch die Liste intist int32_t, und die unterzeichnete Rechtsverschiebung ist eine arithmetische Verschiebung (in C ++ es der Implementierung definiert)
Peter Cordes

1
Übrigens verwendet Visual Studio (mindestens 2013) auch den Test / Setcc / Sub-Ansatz. Ich mag das cmp / adc selbst besser.
DocMax

5

Dies kann mit einem Worst-Case von weniger als 32 Operationen durchgeführt werden:

Prinzip: Überprüfen auf 2 oder mehr Bits ist genauso effizient wie das Überprüfen auf 1 Bit.

Zum Beispiel hindert Sie nichts daran, zuerst zu überprüfen, für welche Gruppierung es sich handelt, und dann jedes Bit vom kleinsten zum größten in dieser Gruppe zu überprüfen.

Also ...
wenn Sie 2 Bits gleichzeitig prüfen, haben Sie im schlimmsten Fall (Nbit / 2) + 1 Schecks insgesamt.
Wenn Sie 3 Bits gleichzeitig prüfen, haben Sie im schlimmsten Fall (Nbit / 3) + 2 Prüfungen insgesamt.
...

Optimal wäre es, Gruppen von 4 Personen einzuchecken. Dies würde im schlimmsten Fall 11 Operationen anstelle Ihrer 32 erfordern.

Der beste Fall reicht von 1 Prüfung Ihrer Algorithmen bis zu 2 Prüfungen, wenn Sie diese Gruppierungsidee verwenden. Aber dieser zusätzliche Scheck im besten Fall lohnt sich für die Einsparungen im schlimmsten Fall.

Hinweis: Ich schreibe es vollständig 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;
}

+1 von mir. Es ist nicht das schnellste, aber es ist schneller als das Original,
Andrew Grant

@ onebyone.livejournal.com: Auch wenn es einen Fehler im Code gab, ist das Konzept der Gruppierung der Punkt, den ich vermitteln wollte. Das eigentliche Codebeispiel spielt keine große Rolle und könnte kompakter, aber weniger effizient gestaltet werden.
Brian R. Bondy

Ich frage mich nur, ob es einen wirklich schlechten Teil meiner Antwort gibt oder ob die Leute das nicht einfach mochten. Ich habe es vollständig ausgeschrieben.
Brian R. Bondy

@ onebyone.livejournal.com: Wenn Sie zwei Algorithmen vergleichen, sollten Sie sie so vergleichen, wie sie sind, ohne davon auszugehen, dass einer durch eine Optimierungsphase magisch transformiert wird. Ich habe nie behauptet, mein Algorithmus sei "schneller". Nur dass es weniger Operationen sind.
Brian R. Bondy

@ onebyone.livejournal.com: ... Ich muss den obigen Code nicht profilieren, um zu wissen, dass es sich um weniger Operationen handelt. Ich kann das deutlich sehen. Ich habe nie Ansprüche geltend gemacht, die eine Profilerstellung erfordern.
Brian R. Bondy

4

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

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 { ...

+1 Dies ist meiner Antwort sehr ähnlich. Die Laufzeit im besten Fall ist schlechter als mein Vorschlag, aber die Laufzeit im schlechtesten Fall ist besser.
Brian R. Bondy

2

Eine andere Methode (Modulteilung und Suche) verdient hier eine besondere Erwähnung aus demselben Link, der von @ anton-tykhyy bereitgestellt wird. Diese Methode ist in der Leistung der DeBruijn-Multiplikations- und Suchmethode sehr ähnlich, mit einem kleinen, aber wichtigen Unterschied.

Modulteilung und Suche

 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 Modulteilungs- und Suchmethode gibt unterschiedliche Werte für v = 0x00000000 und v = FFFFFFFF zurück, während die DeBruijn-Multiplikations- und Suchmethode 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 */

1
modist langsam. Stattdessen können Sie die Original - Multiply-and-Lookup - Methode verwenden und subtrahieren !vvon rdem Rande Fälle zu behandeln.
Eitan T

3
@EitanT ein Optimierer kann diesen Mod in eine schnelle Multiplikation verwandeln, wie in der Freude der Hacker
phuclv

2

Laut der BitScan-Seite zur Schachprogrammierung und meinen eigenen Messungen ist Subtrahieren und Xor schneller als Negieren und Maskieren .

(Beachten Sie, dass, wenn Sie die nachfolgenden Nullen zählen möchten 0, die Methode, wie ich sie habe, zurückgegeben wird, 63während das Negieren und die Maske zurückgegeben werden 0.)

Hier ist eine 64-Bit-Subtraktion 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];

Als Referenz finden Sie hier 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];

Dies (v ^ (v-1))funktioniert vorausgesetzt v != 0. Im Falle, dass v == 0es 0xFF .... FF zurückgibt, während (v & -v)es Null gibt (was übrigens auch falsch ist, buf zumindest führt es zu einem vernünftigen Ergebnis).
CiaPan

@CiaPan: Das ist ein guter Punkt, ich werde es erwähnen. Ich vermute, es gibt eine andere De Bruijn-Zahl, die dies beheben würde, indem 0 in den 63. Index aufgenommen wird.
jnm2

Duh, hier liegt das Problem nicht. 0 und 0x8000000000000000 führen beide zu 0xFFFFFFFFFFFFFFFF nach v ^ (v-1), sodass sie nicht voneinander unterschieden werden können. In meinem Szenario wird niemals Null eingegeben.
jnm2

1

Sie können überprüfen, ob eines der Bits niedrigerer Ordnung gesetzt ist. Wenn ja, dann schauen Sie sich die untere Ordnung der verbleibenden Bits an. z.B,:

32bit int - Überprüfen Sie, ob eine der ersten 16 eingestellt ist. Wenn ja, prüfen Sie, ob eine der ersten 8 eingestellt ist. wenn ja, ....

Wenn nicht, prüfen Sie, ob eine der oberen 16 eingestellt ist.

Im Wesentlichen ist es binäre Suche.


1

In meiner Antwort hier erfahren Sie, wie Sie dies mit einem einzelnen x86-Befehl tun. Mit der Ausnahme, dass Sie zum Auffinden des niedrigstwertigen gesetzten Bits den BSFBefehl ("Bit Scan Forward") anstelle des dort BSRbeschriebenen verwenden möchten .


1

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

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

Um alle 1s von der niedrigstwertigen 1 auf LSB zu bringen, verwenden Sie ((x & -x) - 1) << 1stattdessen
phuclv

ein noch schnellerer Weg:x ^ (x-1)
Phuclv

1
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.

etc.

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 auftreten wird.


3
Und ein Worst-Case von 32 Zweig Fehlvorhersage :)

1
Könnte dies nicht zumindest zu einem Schalter gemacht werden ...?
Steven Lu

"Könnte das nicht wenigstens zu einem Schalter gemacht werden ...?" Haben Sie versucht, dies zu tun, bevor Sie angedeutet haben, dass dies möglich ist? Seit wann können Sie direkt mit den Fällen eines Schalters rechnen? Es ist eine Nachschlagetabelle, keine Klasse.
j riv

1

Fand diesen cleveren Trick mit 'magischen Masken' in "Die Kunst des Programmierens, Teil 4", der es in O (log (n)) Zeit für n-Bit-Zahlen macht. [mit log (n) zusätzlichem Speicherplatz]. Typische Lösungen, die nach dem gesetzten Bit suchen, 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 der nachgestellten 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

Wenn C ++ 11 für Sie verfügbar ist, kann ein Compiler manchmal die Aufgabe 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 ein 1-basierter Index.


1
Clever, aber es wird zu einer katastrophal schlechten Assembly kompiliert, wenn die Eingabe keine Kompilierungszeitkonstante ist. godbolt.org/g/7ajMyT . (Eine dumme Schleife über die Bits mit gcc oder ein tatsächlicher rekursiver Funktionsaufruf mit clang.) Gcc / clang kann ffs()zur Kompilierungszeit ausgewertet werden, sodass Sie dies nicht verwenden müssen, damit die konstante Weitergabe funktioniert. (Inline-Asm müssen Sie natürlich vermeiden.) Wenn Sie wirklich etwas benötigen, das als C ++ 11 funktioniert constexpr, können Sie trotzdem GNU C verwenden __builtin_ffs.
Peter Cordes

0

Dies betrifft die Antwort von @Anton Tykhyy

Hier ist meine C ++ 11 constexpr-Implementierung, die Casts beseitigt und eine Warnung in VC ++ 17 entfernt, indem ein 64-Bit-Ergebnis auf 32 Bit gekürzt 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 zu umgehen, dass 0x1 und 0x0 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 will, 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

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

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

-3

Vor kurzem habe ich gesehen, dass Singapurs Premier ein Programm gepostet hat, das er auf Facebook geschrieben hat. Es gibt eine Zeile, in der es erwähnt wird.

Die Logik ist einfach "Wert & Wert", angenommen, Sie haben 0x0FF0, dann 0FF0 & (F00F + 1), was 0x0010 entspricht, was bedeutet, dass die niedrigste 1 im 4. Bit ist .. :)


1
Dies isoliert das niedrigste Bit, gibt Ihnen jedoch nicht die Position, nach der diese Frage fragt.
Rhashimoto

Ich denke auch nicht, dass dies funktioniert, um das letzte Stück zu finden.
Yyny

Wert & ~ Wert ist 0.
Khw

Ups, meine Augen werden schlecht. Ich habe ein Minus mit einer Tilde verwechselt. ignoriere meinen Kommentar
khw

-8

Wenn Sie über die Ressourcen verfügen, können Sie Speicher opfern, um die Geschwindigkeit zu verbessern:

static const unsigned bitPositions[MAX_INT] = { 0, 0, 1, 0, 2, /* ... */ };

unsigned GetLowestBitPos(unsigned value)
{
    assert(value != 0); // handled separately
    return bitPositions[value];
}

Hinweis: Diese Tabelle würde mindestens 4 GB verbrauchen (16 GB, wenn wir den Rückgabetyp als belassen unsigned). Dies ist ein Beispiel für den Handel einer begrenzten Ressource (RAM) gegen eine andere (Ausführungsgeschwindigkeit).

Wenn Ihre Funktion portabel bleiben und um jeden Preis so schnell wie möglich ausgeführt werden muss, ist dies der richtige Weg. In den meisten realen Anwendungen ist eine 4-GB-Tabelle unrealistisch.


1
Der Bereich der Eingabe wird bereits durch den Parametertyp festgelegt. 'Unsigned' ist ein 32-Bit-Wert. Nein, es geht Ihnen nicht gut.
Brian

3
ähm ... hat Ihr mythisches System und Betriebssystem ein Konzept des ausgelagerten Speichers? Wie viel Zeit wird das kosten?
Mikeage

14
Dies ist keine Antwort. Ihre Lösung ist in ALLEN realen Anwendungen völlig unrealistisch und es ist unaufrichtig, sie als "Kompromiss" zu bezeichnen. Ihr mythisches System mit 16 GB RAM für eine einzelne Funktion existiert einfach nicht. Sie hätten auch geantwortet "Verwenden Sie einen Quantencomputer".
Brian

3
Gedächtnis für Geschwindigkeit opfern? Eine Nachschlagetabelle mit mehr als 4 GB wird auf keinem derzeit vorhandenen Computer in den Cache passen. Daher würde ich mir vorstellen, dass dies wahrscheinlich langsamer ist als fast alle anderen Antworten hier.

1
Argh. Diese schreckliche Antwort verfolgt mich immer wieder :)@Dan: Sie haben Recht mit dem Zwischenspeichern von Speicher. Siehe den Kommentar von Mikeage oben.
e.James
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.