कम से कम महत्वपूर्ण बिट की स्थिति जो सेट की गई है


120

मैं कम से कम महत्वपूर्ण बिट की स्थिति निर्धारित करने के लिए एक कुशल तरीके की तलाश कर रहा हूं जो कि पूर्णांक में सेट है, उदाहरण के लिए 0x0FF0 के लिए यह 4 होगा।

एक तुच्छ कार्यान्वयन यह है:

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

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

किसी भी विचार कैसे इसे से बाहर कुछ चक्र निचोड़ करने के लिए?

(नोट: यह प्रश्न ऐसे लोगों के लिए है, जो ऐसी चीजों का आनंद लेते हैं, न कि लोगों द्वारा मुझे बताने के लिए कि xyzoptimization बुराई है।)

[संपादित करें] विचारों के लिए सभी को धन्यवाद! मैंने कुछ और चीजें भी सीखी हैं। ठंडा!


जबकि (मूल्य _N >> (++ पॉज़))! = 0);
थॉमस

जवाबों:


170

Bit Twiddling Hacks , प्रदर्शन, अनुकूलन चर्चा के साथ, जुड़ाव, हैक का एक उत्कृष्ट संग्रह प्रदान करता है। आपकी समस्या के लिए मेरा पसंदीदा समाधान (उस साइट से) «गुणा और लुकिंग» है:

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

उपयोगी संदर्भ:


18
क्यों होता है पतन? यह संभवतः गुणा का गति के आधार पर सबसे तेज़ कार्यान्वयन है। यह निश्चित रूप से कोड कॉम्पैक्ट है, और (v & v) चाल कुछ ऐसी है जिसे हर किसी को सीखना चाहिए और याद रखना चाहिए।
एडम डेविस

2
+1 बहुत अच्छा है, यदि एक (X & Y) ऑपरेशन की तुलना में एक महंगा ऑपरेशन कितना महंगा है?
ब्रायन आर। बॉन्डी

4
किसी को भी पता है कि कैसे इस के प्रदर्शन की तुलना करता है __builtin_ffslया ffsl?
स्टीवन लू

2
@Jim Balter, लेकिन मॉडुलो आधुनिक हार्डवेयर पर गुणा की तुलना में बहुत धीमा है। इसलिए मैं इसे बेहतर समाधान नहीं कहूंगा।
अप्रीरी

2
यह मुझे लगता है कि दोनों 0x01 और 0x00 के मान से 0 मान परिणाम है। जाहिरा तौर पर यह चाल बताएगी कि सबसे कम बिट सेट है यदि 0 में पारित किया गया हो!
abelenky

80

क्यों नहीं में निर्मित ffs का उपयोग करें ? (मैंने लिनक्स से एक मैन पेज पकड़ा है, लेकिन यह उससे अधिक व्यापक रूप से उपलब्ध है।)

ffs (3) - लिनक्स मैन पेज

नाम

ffs - किसी शब्द में पहले बिट सेट करें

सार

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

विवरण

एफएफएस () फ़ंक्शन शब्द i में पहले (सबसे कम महत्वपूर्ण) बिट सेट की स्थिति देता है। कम से कम महत्वपूर्ण बिट स्थिति 1 और सबसे महत्वपूर्ण स्थिति है जैसे 32 या 64. फ़ंक्शंस एफएफ़एसएल () और एफएफ़एसएल () एक ही करते हैं लेकिन संभवतः विभिन्न आकार के तर्क लेते हैं।

प्रतिलाभ की मात्रा

ये फ़ंक्शन पहले बिट सेट की स्थिति लौटाते हैं, या यदि कोई बिट्स i में सेट नहीं किया जाता है।

के अनुरूप करना

4.3BSD, POSIX.1-2001।

टिप्पणियाँ

बीएसडी सिस्टम में एक प्रोटोटाइप है <string.h>


6
FYI करें, यह उपलब्ध होने पर संबंधित असेंबली कमांड में संकलित किया जाता है।
Jérémie

46

एक x86 असेंबली इंस्ट्रक्शन ( bsf) है जो इसे करेगा। :)

अधिक अनुकूलित ?!

पक्षीय लेख:

इस स्तर पर अनुकूलन स्वाभाविक रूप से वास्तुकला पर निर्भर है। आज के प्रोसेसर बहुत जटिल हैं (शाखा भविष्यवाणी, कैश मिस, पाइपलाइनिंग के संदर्भ में) यह अनुमान लगाना बहुत कठिन है कि किस आर्किटेक्चर पर किस कोड को तेजी से निष्पादित किया जाता है। 32 से 9 तक घटते हुए संचालन या ऐसी चीजें जो कुछ आर्किटेक्चर पर प्रदर्शन को कम कर सकती हैं। एकल आर्किटेक्चर पर अनुकूलित कोड दूसरे में बदतर कोड हो सकता है। मुझे लगता है कि आप या तो एक विशिष्ट सीपीयू के लिए इसे ऑप्टिमाइज़ करेंगे या इसे छोड़ देंगे जैसा कि यह है और संकलक को यह चुनने दें कि यह क्या बेहतर है।


20
@dwc: मैं समझता हूं, लेकिन मुझे लगता है कि यह क्लॉज: "किसी भी विचार से कुछ चक्रों को कैसे निचोड़ना है?" इस तरह के जवाब को पूरी तरह स्वीकार्य बनाता है!
मेहरदाद अफश्री

5
+1 उसका उत्तर आवश्यक रूप से उसकी वास्तुकला पर निर्भरता के कारण है, इसलिए विधानसभा के निर्देशों का पालन करना पूरी तरह से मान्य उत्तर है।
क्रिस लुत्ज

3
+1 चतुर उत्तर, हां यह C या C ++ नहीं है, लेकिन यह नौकरी के लिए सही उपकरण है।
एंड्रयू हरे

1
रुको, कभी नहीं। पूर्णांक का वास्तविक मूल्य यहां कोई मायने नहीं रखता है। माफ़ करना।
क्रिस लुट्ज़

2
@ बास्टियन: वे ZF = 1 सेट करते हैं अगर ओपेरा शून्य है।
मेहरदाद आफश्री

43

अधिकांश आधुनिक आर्किटेक्चर के पास सबसे कम सेट बिट की स्थिति का पता लगाने के लिए कुछ निर्देश होंगे, या सबसे अधिक सेट बिट, या अग्रणी शून्य की संख्या आदि की गिनती होगी।

यदि आपके पास इस वर्ग का कोई एक निर्देश है तो आप दूसरों को सस्ते में अनुकरण कर सकते हैं।

कागज पर इसके माध्यम से काम करने के लिए कुछ समय निकालें और महसूस करें कि x & (x-1)x में सबसे कम सेट बिट को साफ करेगा, और ( x & ~(x-1) )सबसे कम सेट बिट को वापस करेगा, भले ही एचीटेक्चर, शब्द की लंबाई आदि के बावजूद, यह जानते हुए, हार्डवेयर काउंट-अग्रणी का उपयोग करना तुच्छ है -जरोज / उच्चतम-सेट-बिट को सबसे कम सेट बिट खोजने के लिए अगर ऐसा करने के लिए कोई स्पष्ट निर्देश नहीं है।

यदि कोई प्रासंगिक हार्डवेयर सपोर्ट नहीं है, तो बिट-टिडलिंग हैक्स पेज पर यहां दिए गए काउंटिंग -लीडिंग- जीरो के बहुप्रतीक्षित और लुकअप कार्यान्वयन को उपर्युक्त पहचानों का उपयोग करके सबसे कम सेट बिट देने के लिए तुच्छ रूप से परिवर्तित किया जा सकता है और शाखाहीन होने का फायदा है।


18

वेई, समाधान का भार और दृष्टि में बेंचमार्क नहीं। आप लोगों को खुद पर शर्म आनी चाहिए ;-)

मेरी मशीन एक इंटेल i530 (2.9 गीगाहर्ट्ज) है, जो विंडोज 7 64-बिट चल रहा है। मैंने मिनगॉव के 32-बिट संस्करण के साथ संकलित किया।

$ 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

मेरा कोड:

#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
डी ब्रूजन और लुकअप दोनों के लिए बेंचमार्क भ्रामक हो सकते हैं - जैसे कि एक तंग लूप में बैठे, पहले ऑपरेशन के बाद प्रत्येक प्रकार के लुकअप टेबल को अंतिम लूप के बाद तक एल 1 कैश में पिन किया जाएगा। यह वास्तविक दुनिया के उपयोग से मेल खाने की संभावना नहीं है।
18

1
कम बाइट में शून्य के साथ इनपुट के लिए, यह पॉइंटर-कास्ट की वजह से शिफ्टिंग के बजाय भंडारण / पुनः लोड करके उच्च बाइट्स प्राप्त करता है। (पूरी तरह से अनावश्यक BTW, और यह एक बदलाव के विपरीत एंडियन-निर्भर बनाता है)। वैसे भी, हॉट कैश की वजह से न केवल माइक्रोबैनमार्क अवास्तविक है, बल्कि इसमें शाखा प्रीडिक्टर्स प्राइमेड और टेस्ट इनपुट्स भी हैं जो बहुत अच्छी भविष्यवाणी करते हैं और LUT कम काम करते हैं। कई वास्तविक उपयोग-मामलों में परिणाम का एक समान वितरण होता है, इनपुट नहीं।
पीटर कॉर्ड्स

2
आपका एफएफएस लूप दुर्भाग्य से बीएसएफ इंस्ट्रक्शन में एक झूठी निर्भरता से धीमा हो जाता है, जो आपके क्रस्टी पुराने कंपाइलर से नहीं बचता है ( लेकिन नए जीसीसी को चाहिए, पॉपकान्ट / lzcnt / tzcnt के लिए भी ऐसा ही होना चाहिएBSFइसके आउटपुट पर एक झूठी निर्भरता है। वास्तविक व्यवहार के बाद से) जब इनपुट = 0 आउटपुट को अपरिवर्तित छोड़ना है)। gcc दुर्भाग्य से लूप पुनरावृत्तियों के बीच के रजिस्टर को क्लीयर न करके इसे लूप-आधारित निर्भरता में बदल देता है। इसलिए लूप को प्रति 5 चक्र पर चलाना चाहिए, BSF (3) + CMOV से टोंटी। (२) विलंबता।
पीटर कॉर्ड्स

1
आपके बेंचमार्क ने पाया कि LUT में FFS पद्धति का लगभग दुगुना प्रवाह है, जो मेरे स्थैतिक-विश्लेषण की भविष्यवाणी से बहुत अच्छी तरह मेल खाता है :)। ध्यान दें कि आप थ्रूपुट माप रहे हैं, विलंबता नहीं, क्योंकि आपके लूप में एकमात्र सीरियल निर्भरता कुल में समेटी गई है। झूठी निर्भरता के बिना, ffs()प्रति घड़ी (3 uops, बीएसएफ के लिए 1 और CMOV के लिए 2, और वे अलग-अलग बंदरगाहों पर चल सकते हैं) एक थ्रूपुट होना चाहिए था। एक ही लूप ओवरहेड के साथ, यह 7 ALU उप्स है जो प्रति घड़ी 3 पर (आपके CPU पर) चला सकते हैं। ओवरहेड हावी है! स्रोत: agner.org/optimize
पीटर कॉर्ड्स

1
हां, आउट-ऑफ-ऑर्डर निष्पादन लूप के कई पुनरावृत्तियों को ओवरलैप कर सकता है यदि एक इनपुट के रूप में bsf ecx, [ebx+edx*4]नहीं माना ecxजाता है जिसके लिए इंतजार करना पड़ता था। (ECX पिछली बार iteraton के CMOV द्वारा लिखा गया था)। लेकिन सीपीयू उस तरह से व्यवहार करता है, "स्रोत शून्य होने पर छुट्टी न दें" को लागू करने के लिए व्यवहार करें (इसलिए यह वास्तव में एक गलत चित्रण नहीं है जैसे कि यह TZCNT के लिए है; एक डेटा निर्भरता की आवश्यकता है क्योंकि धारणा पर कोई शाखा या सट्टा निष्पादन नहीं है; यह इनपुट गैर-शून्य है)। ईसीएक्स पर निर्भरता को तोड़ने के लिए हम xor ecx,ecxपहले जोड़कर इसे दूर कर सकते हैं bsf
पीटर कॉर्ड्स

17

इसका सबसे तेज़ (गैर-आंतरिक / गैर-संयोजनकर्ता) समाधान सबसे कम-बाइट को खोजने के लिए है और फिर 256-प्रविष्टि लुकअप तालिका में उस बाइट का उपयोग करें। यह आपको चार सशर्त निर्देशों का सबसे खराब प्रदर्शन और 1. का सबसे अच्छा मामला देता है। न केवल यह निर्देशों का सबसे कम राशि है, बल्कि आधुनिक हार्डवेयर पर कम से कम शाखाओं की मात्रा भी महत्वपूर्ण है।

आपकी तालिका (256 8-बिट प्रविष्टियों) को 0-255 की सीमा में प्रत्येक संख्या के लिए एलएसबी का सूचकांक होना चाहिए। आप अपने मूल्य के प्रत्येक बाइट की जांच करते हैं और सबसे कम गैर-शून्य बाइट पाते हैं, फिर इस मूल्य का उपयोग वास्तविक सूचकांक देखने के लिए करते हैं।

इसके लिए 256-बाइट्स मेमोरी की आवश्यकता होती है, लेकिन यदि इस फ़ंक्शन की गति इतनी महत्वपूर्ण है, तो 256-बाइट्स इसके लायक है,

उदाहरण के लिए

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
यह वास्तव में तीन शर्तों का सबसे खराब मामला है :) लेकिन हां, यह सबसे तेज़ तरीका है (और आमतौर पर लोग जो साक्षात्कार के सवालों की तरह देख रहे हैं)।
ब्रायन

4
क्या आपको कहीं पर +8, +16, 16:30 नहीं चाहिए?
मार्क रैनसम

7
कोई भी लुकअप तालिका कैश मिस की संभावना को बढ़ाती है और मेमोरी एक्सेस की लागत को बढ़ा सकती है जो निर्देशों को निष्पादित करने से अधिक परिमाण के कई आदेश हो सकते हैं।
मेहरदाद अफश्री

1
मैं भी बिट-शिफ्ट (प्रत्येक बार इसे 8 से स्थानांतरित करना) का उपयोग करूंगा। पूरी तरह से रजिस्टरों का उपयोग करके किया जा सकता है। पॉइंटर्स का उपयोग करके, आपको मेमोरी एक्सेस करनी होगी।
जोहान्स शाउब -

1
उचित समाधान, लेकिन लुकअप तालिका के कैश में होने की संभावना के बीच (जो कि हल किया जा सकता है, जैसा कि बताया गया है) और शाखाओं की संख्या (संभावित शाखा गलतफहमी), मैं बहु-प्रकार और लुकअप समाधान (कोई शाखा नहीं) पसंद करता हूं छोटी देखने की मेज)। बेशक, यदि आप आंतरिक या इनलाइन विधानसभा का उपयोग कर सकते हैं, तो वे शायद एक बेहतर विकल्प हैं। फिर भी, यह समाधान बुरा नहीं है।

13

ओएमजी में यह सिर्फ सर्पिल है।

इनमें से अधिकांश उदाहरणों की कमी है, सभी हार्डवेयर कैसे काम करते हैं, इसके बारे में थोड़ी समझ है।

किसी भी समय आपके पास एक शाखा है, सीपीयू को यह अनुमान लगाना होगा कि कौन सी शाखा ली जाएगी। निर्देश पाइप को उन निर्देशों के साथ लोड किया गया है जो अनुमानित पथ का नेतृत्व करते हैं। यदि सीपीयू ने गलत अनुमान लगाया है, तो निर्देश पाइप बह जाता है, और दूसरी शाखा को लोड करना होगा।

शीर्ष पर लूप करते समय सरल पर विचार करें। अनुमान लूप के भीतर रहने का होगा। यह कम से कम एक बार गलत होगा जब यह लूप छोड़ देगा। यह निर्देश पाइप को फ्लश करेगा। यह व्यवहार अनुमान लगाने से थोड़ा बेहतर है कि यह लूप को छोड़ देगा, जिस स्थिति में यह प्रत्येक पुनरावृत्ति पर निर्देश पाइप को फ्लश करेगा।

खो जाने वाले सीपीयू चक्रों की मात्रा एक प्रकार के प्रोसेसर से अगले तक भिन्न होती है। लेकिन आप 20 और 150 खो सीपीयू चक्रों के बीच की उम्मीद कर सकते हैं।

अगला बदतर समूह वह है जहां आप सोचते हैं कि मूल्य को छोटे टुकड़ों में विभाजित करके और कई और शाखाओं को जोड़कर कुछ पुनरावृत्तियों को बचाया जा सकता है। इन शाखाओं में से प्रत्येक अनुदेश पाइप को फ्लश करने का एक अतिरिक्त अवसर जोड़ता है और एक और 20 से 150 घड़ी चक्र खर्च करता है।

जब आप किसी तालिका में मान देखते हैं तो क्या होता है, इस पर विचार करें। संभावना है कि मूल्य वर्तमान में कैश में नहीं है, कम से कम पहली बार जब आपका फ़ंक्शन नहीं कहा जाता है। इसका मतलब है कि कैश से मूल्य लोड होने के दौरान सीपीयू ठप हो जाता है। फिर से यह एक मशीन से दूसरी मशीन में भिन्न होता है। नए इंटेल चिप्स वास्तव में धागे को स्वैप करने के अवसर के रूप में उपयोग करते हैं जबकि वर्तमान धागा कैश लोड के पूरा होने की प्रतीक्षा कर रहा है। यह आसानी से एक निर्देश पाइप फ्लश की तुलना में अधिक महंगा हो सकता है, हालांकि यदि आप इस ऑपरेशन को कई बार कर रहे हैं तो यह केवल एक बार होने की संभावना है।

स्पष्ट रूप से सबसे तेज़ निरंतर समय समाधान वह है जिसमें नियतात्मक गणित शामिल है। एक शुद्ध और सुरुचिपूर्ण समाधान।

अगर यह पहले से ही कवर किया गया था तो मेरी माफी।

XCODE AFAIK को छोड़कर हर कंपाइलर का उपयोग होता है, जिसमें फॉरवर्ड बिटकॉन्स और रिवर्स बिटस्कैन दोनों के लिए कंपाइलर इंट्रिंसिक्स होते हैं। ये बिना कैश मिस, किसी ब्रांच मिस-प्रेडिक्शन और किसी अन्य प्रोग्रामर द्वारा स्टंबलिंग ब्लॉक उत्पन्न करने वाले अधिकांश हार्डवेयर पर एक सिंगल असेंबली इंस्ट्रक्शन को संकलित करेंगे।

Microsoft कंपाइलर्स के लिए _BitScanForward और _BitScanReverse का उपयोग करें।
GCC के लिए __builtin_ffs, __builtin_clz, __builtin_ctz का उपयोग करें।

इसके अतिरिक्त, यदि आप चर्चा किए जा रहे विषय के बारे में पर्याप्त रूप से जानकार नहीं हैं, तो कृपया एक उत्तर और संभावित रूप से भ्रामक नवागंतुकों को पोस्ट करने से बचें।

क्षमा करें मैं एक समाधान प्रदान करना पूरी तरह से भूल गया .. यह वह कोड है जो मैं IPAD पर उपयोग करता हूं जिसमें कार्य के लिए कोई विधानसभा स्तर निर्देश नहीं है:

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

यहाँ समझने वाली बात यह है कि यह वह तुलना नहीं है जो महंगी है, बल्कि तुलना के बाद होने वाली शाखा है। इस मामले में तुलना .. == 0 के साथ 0 या 1 के मान के लिए मजबूर की जाती है, और परिणाम का उपयोग शाखा के दोनों ओर होने वाले गणित को संयोजित करने के लिए किया जाता है।

संपादित करें:

उपरोक्त कोड पूरी तरह से टूट गया है। यह कोड काम करता है और अभी भी शाखा-मुक्त है (यदि अनुकूलित है):

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

यह रिटर्न -1 अगर दिया गया है। यदि आप 0 की परवाह नहीं करते हैं या 0 के लिए 31 प्राप्त करने में खुश हैं, तो i0 गणना को हटा दें, समय की एक बचत।


3
मैंने इसे आपके लिए ठीक कर दिया है। आप जो पोस्ट करते हैं उसका परीक्षण अवश्य करें।
जिम बेल्टर

5
जब आप इसमें एक टर्नरी ऑपरेटर शामिल करते हैं, तो आप इसे "शाखा-मुक्त" कैसे कह सकते हैं?
बोल्ट

2
इसकी एक सशर्त चाल है। एक एकल असेंबली भाषा निर्देश जो पैरामीटर के रूप में दोनों संभव मान लेता है, और सशर्त के मूल्यांकन के आधार पर एक मूव ऑपरेशन करता है। और इस प्रकार "ब्रांच फ्री" है। किसी अन्य अज्ञात या संभवतः गलत पते पर कोई छलांग नहीं है।
डैन


7

इस तरह की पोस्ट से प्रेरित है जिसमें एक सेट बिट के लिए खोज शामिल है, मैं निम्नलिखित प्रदान करता हूं:

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

पेशेवरों:

  • नहीं छोरों
  • कोई शाखा नहीं
  • निरंतर समय में चलता है
  • एक अन्यथा परिणाम के वापस लौटने से मूल्य = 0 को संभालता है
  • कोड की केवल दो लाइनें

विपक्ष:

  • कोडन के रूप में थोड़ा धीरज रखता है (स्थिरांक बदलकर इसे ठीक किया जा सकता है)
  • मानता है कि डबल एक वास्तविक है * 8 IEEE फ्लोट (IEEE 754)

अद्यतन: जैसा कि टिप्पणियों में बताया गया है, एक संघ एक क्लीनर कार्यान्वयन है (सी के लिए, कम से कम) और जैसा दिखेगा:

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

यह सब कुछ के लिए 32-बिट इन्टस को थोड़ा-एंडियन स्टोरेज के साथ मानता है (x86 प्रोसेसर सोचें)।


1
दिलचस्प है - मैं अभी भी बिट अंकगणित के लिए डबल्स का उपयोग करने से डरता हूं, लेकिन मैं इसे ध्यान में
रखूंगा

Frexp () का उपयोग कर इसे थोड़ा और पोर्टेबल बना सकते हैं
aka.nice

1
पॉइंटर-कास्टिंग द्वारा टाइप-पाइंटिंग C या C ++ में सुरक्षित नहीं है। C ++ में memcpy का उपयोग करें, या C. में एक यूनियन (या C ++ में एक यूनियन है यदि आपका कंपाइलर इसकी गारंटी देता है। उदाहरण के लिए, ग्नू एक्सटेंशन C ++ के लिए (कई कंपाइलरों द्वारा समर्थित) करते हैं, यूनियन टाइप-पिंगिंग सुरक्षित है।)
पीटर कॉर्ड्स

1
पुराने gcc भी पॉइंटर-कास्ट के बजाय एक यूनियन के साथ बेहतर कोड बनाते हैं: यह एक FP reg (xmm0) से सीधे स्टोर करने / पुनः लोड करने के बजाय rax (movq के साथ) में जाता है। नए gcc और clang दोनों तरह से movq का उपयोग करते हैं। यूनियन वर्जन के लिए godbolt.org/g/x7JBiL देखें । क्या यह जानबूझकर है कि आप 20 तक अंकगणितीय बदलाव कर रहे हैं? अपनी मान्यताओं भी सूची है कि ऐसा करना चाहिए intहै int32_t, और कहा कि हस्ताक्षर किए सही पारी अंकगणितीय पारी (C ++ यह के कार्यान्वयन से परिभाषित) है
पीटर Cordes

1
इसके अलावा BTW, विजुअल स्टूडियो (2013 कम से कम) भी परीक्षण / सेटक / उप दृष्टिकोण का उपयोग करता है। मैं खुद को cmp / adc ज्यादा पसंद करता हूं।
डॉकमैक्स

5

यह 32 से कम परिचालनों के सबसे बुरे मामले के साथ किया जा सकता है:

सिद्धांत: 2 या अधिक बिट्स के लिए जाँच करना केवल 1 बिट के लिए जाँच के रूप में कुशल है।

इसलिए उदाहरण के लिए आपको कुछ भी चेक करने से नहीं रोक रहा है, जिसके लिए पहले ग्रुप बनाना है, फिर उस ग्रुप में सबसे छोटे से लेकर सबसे बड़े तक प्रत्येक बिट को चेक करना है।

इसलिए ...
यदि आप एक बार में 2 बिट्स की जांच करते हैं तो आपके पास सबसे खराब स्थिति में है (Nbits / 2) + 1 चेक।
यदि आप एक बार में 3 बिट्स की जांच करते हैं तो आपके पास सबसे खराब स्थिति (Nbits / 3) + 2 की कुल जांच होती है।
...

इष्टतम 4 के समूहों में जांचना होगा। आपके 32 के बजाय सबसे खराब स्थिति में 11 ऑपरेशन की आवश्यकता होगी।

यदि आप इस समूहीकरण विचार का उपयोग करते हैं तो सबसे अच्छा मामला आपके एल्गोरिदम के 1 चेक से 2 चेक तक जाता है। लेकिन सबसे अच्छा मामले में अतिरिक्त 1 चेक सबसे खराब स्थिति बचत के लिए इसके लायक है।

नोट: मैं इसे लूप का उपयोग करने के बजाय पूर्ण रूप से लिखता हूं क्योंकि यह उस तरह से अधिक कुशल है।

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। यह सबसे तेज़ नहीं है, लेकिन यह मूल की तुलना में तेज़ है, जो कि बिंदु था ...
एंड्रयू ग्रांट

@ onebyone.livejournal.com: भले ही कोड में एक बग था, समूहीकरण की अवधारणा वह बिंदु है जिसे मैं पार करने की कोशिश कर रहा था। वास्तविक कोड नमूना ज्यादा मायने नहीं रखता है, और इसे अधिक कॉम्पैक्ट लेकिन कम कुशल बनाया जा सकता है।
ब्रायन आर। बॉन्डी

मैं बस सोच रहा था कि क्या मेरे जवाब का कोई बुरा हिस्सा है, या अगर लोगों को यह पसंद नहीं आया तो मैंने इसे पूरा लिखा?
ब्रायन आर। बॉन्डी

@ onebyone.livejournal.com: जब आप 2 एल्गोरिदम की तुलना करते हैं, तो आपको उनकी तुलना उसी तरह से करनी चाहिए, जैसे यह मानकर नहीं कि किसी को एक अनुकूलन चरण द्वारा जादुई रूप से बदल दिया जाएगा। मैंने कभी दावा नहीं किया कि मेरा एल्गोरिथ्म या तो "तेज" था। केवल यह कि यह कम परिचालन है।
ब्रायन आर। बॉन्डी

@ onebyone.livejournal.com: ... मुझे यह जानने के लिए उपरोक्त कोड की आवश्यकता नहीं है कि यह कम परिचालन है। मैं स्पष्ट देख सकता हूं। मैंने कभी ऐसा कोई दावा नहीं किया, जिसमें प्रोफाइलिंग की आवश्यकता हो।
ब्रायन आर। बॉन्डी

4

बाइनरी खोज का उपयोग क्यों नहीं करते ? यह हमेशा 5 ऑपरेशन के बाद पूरा होगा (4 बाइट्स का अंतर आकार):

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 यह मेरे उत्तर के समान है। सबसे अच्छा केस रन टाइम मेरे सुझाव से भी बदतर है, लेकिन सबसे खराब स्थिति रन टाइम बेहतर है।
ब्रायन आर। बॉन्डी

2

एक अन्य विधि (मापांक विभाजन और खोज) @ anton-tykhyy द्वारा प्रदान किए गए उसी लिंक से यहां एक विशेष उल्लेख के योग्य है । यह विधि DeBruijn के प्रदर्शन में बहुत समान है और मामूली लेकिन महत्वपूर्ण अंतर के साथ लुकअप विधि।

मापांक विभाजन और खोज

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

मापांक विभाजन और देखने का तरीका v = 0x00000000 और v = FFFFFFFF के लिए अलग-अलग मान देता है जबकि DeBruijn गुणा और लुकअप विधि दोनों इनपुट पर शून्य देता है।

परीक्षा:-

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
modधीमा है। इसके बजाय, आप मूल गुणा और देखने विधि का उपयोग करें और घटा सकते !vसे rबढ़त मामलों को संभालने के लिए।
Eitan T

3
@ EitanT एक ऑप्टिमाइज़र उस मोड को हैकर्स की
ख़ुशी की

2

के अनुसार BitScan पेज प्रोग्रामिंग शतरंज और अपने माप, घटाना और XOR निगेट की तुलना में तेजी और मुखौटा है।

(ध्यान दें कि यदि आप पीछे चल रहे शून्य को गिनने जा रहे हैं 0, तो विधि के रूप में मेरे पास यह रिटर्न है 63जबकि नकारात्मक और मुखौटा रिटर्न 0।)

यहाँ एक 64-बिट घटाव और 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];

संदर्भ के लिए, यहां नकारात्मक और मुखौटा विधि का 64-बिट संस्करण है:

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

यह (v ^ (v-1))काम प्रदान करता है v != 0। के मामले में v == 0यह 0xFF देता है .... एफएफ whilst (v & -v)शून्य देता है (जो वैसे भी गलत है, कम से कम, यह उचित परिणाम की ओर जाता है)।
सियापन

@CiaPan: यह एक अच्छी बात है, मैं इसका उल्लेख करूँगा। मैं अनुमान लगा रहा हूं कि एक अलग डी ब्रूजन नंबर है जो 63 वें इंडेक्स में 0 डालकर इसे हल करेगा।
jnm2

ओह, यह मुद्दा नहीं है। 0 और 0x8000000000000000 के बाद दोनों परिणाम 0xFFFFFFFFFFFFFFFFFFFF में हैं v ^ (v-1), इसलिए उन्हें अलग नहीं बताया जा रहा है। मेरे परिदृश्य में, शून्य कभी भी इनपुट नहीं होगा।
jnm2

1

आप जांच कर सकते हैं कि क्या कोई निचला क्रम बिट सेट है। यदि ऐसा है तो शेष बिट्स के निचले क्रम को देखें। जैसे ,:

32 बिट इंट - चेक करें कि क्या पहले 16 में से कोई सेट किया गया है। यदि हां, तो जांच लें कि क्या पहले 8 में से कोई भी सेट है। यदि ऐसा है तो, ....

यदि नहीं, तो जांच लें कि कोई ऊपरी 16 सेट है या नहीं।

अनिवार्य रूप से यह द्विआधारी खोज है।


1

एक एकल x86 निर्देश के साथ कैसे करें, इसके लिए मेरा जवाब यहां देखें , सिवाय इसके कि वहां वर्णित कम से कम महत्वपूर्ण सेट को आप चाहते हैं BSF("बिट स्कैन फॉरवर्ड") निर्देश BSR


1

अभी तक एक और समाधान, सबसे तेज संभव नहीं है, लेकिन काफी अच्छा लगता है।
कम से कम इसकी कोई शाखा नहीं है। ;)

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को कम से कम महत्वपूर्ण 1 से एलएसबी में प्राप्त करने के लिए , ((x & -x) - 1) << 1इसके बजाय का उपयोग करें
phuclv

इससे भी तेज तरीका:x ^ (x-1)
फुलव्व

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% कोड की पहली पंक्ति पर वापस आ जाएगा।

सभी संख्याओं का 75% कोड की पहली 2 पंक्तियों पर वापस आ जाएगा।

सभी संख्याओं का 87% कोड की पहली 3 पंक्तियों में वापस आ जाएगा।

सभी संख्याओं का 94% कोड की पहली 4 लाइनों में वापस आ जाएगा।

सभी संख्याओं का 97% कोड की पहली 5 पंक्तियों में वापस आ जाएगा।

आदि।

मुझे लगता है कि जो लोग इस कोड के लिए सबसे खराब स्थिति के मामले में अक्षम हैं, उन्हें समझ में नहीं आता कि यह स्थिति कितनी दुर्लभ है।


3
और 32 शाखा दुष्प्रचार का सबसे बुरा मामला :)

1
यह कम से कम एक स्विच में नहीं किया जा सकता है ...?
स्टीवन लू

"यह कम से कम एक स्विच में नहीं किया जा सकता है ...?" क्या आपने इसे लागू करने से पहले ऐसा करने की कोशिश की? जब से आप एक स्विच के मामलों पर सही गणना कर सकते हैं? यह लुकअप टेबल है, क्लास नहीं।
j riv

1

"प्रोग्रामिंग की कला, भाग 4" में 'मैजिक मास्क' का उपयोग करके इस चतुर चाल को पाया, जो इसे n-बिट संख्या के लिए O (लॉग (n)) समय में करता है। [लॉग (n) अतिरिक्त जगह के साथ]। सेट बिट के लिए विशिष्ट समाधानों की जाँच या तो O (n) है या O (n) को एक लुक अप टेबल के लिए अतिरिक्त स्थान की आवश्यकता है, इसलिए यह एक अच्छा समझौता है।

मैजिक मास्क:

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

मुख्य विचार: 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

यदि C ++ 11 आपके लिए उपलब्ध है, तो एक संकलक कभी-कभी आपके लिए कार्य कर सकता है :)

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

परिणाम 1-आधारित सूचकांक है।


1
चतुर, लेकिन यह विनाशकारी रूप से खराब विधानसभा के लिए संकलित करता है जब इनपुट एक संकलन-समय स्थिर नहीं होता है। godbolt.org/g/7ajMyT । (जीसीसी के साथ बिट्स पर एक गूंगा लूप, या क्लैंग के साथ एक वास्तविक पुनरावर्ती फ़ंक्शन कॉल।) जीसीसी / क्लैंग ffs()संकलन समय पर मूल्यांकन कर सकता है , इसलिए आपको काम करने के लिए निरंतर-प्रसार के लिए इसका उपयोग करने की आवश्यकता नहीं है। (आपको इनलाइन-एएसएम से बचना होगा, निश्चित रूप से।) यदि आपको वास्तव में कुछ ऐसी चीज़ की ज़रूरत है जो C ++ 11 के रूप में काम करती है constexpr, तो भी आप GNU C का उपयोग कर सकते हैं __builtin_ffs
पीटर कॉर्डेस

0

यह @Anton Tykhyy उत्तर के संबंध में है

यहाँ मेरा C ++ 11 कॉन्स्ट्रेप कार्यान्वयन जातियों के साथ किया जा रहा है और VC बिट 17 पर एक चेतावनी को हटाकर 64 बिट्स को 32 बिट्स पर परिणामित कर रहा है:

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

0x1 और 0x0 के मुद्दे को हल करने के लिए, दोनों 0 कर रहे हैं:

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

लेकिन अगर कंपाइलर कॉल को प्रीप्रोसेस नहीं कर सकता है या नहीं कर रहा है तो यह गणना के लिए कुछ चक्र जोड़ देगा।

अंत में, यदि रुचि है, तो यहां यह देखने के लिए कि क्या करना है, यह जाँचने के लिए स्थैतिक की एक सूची दी गई है:

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

यहां एक सरल विकल्प है, भले ही लॉग ढूंढना थोड़ा महंगा हो।

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

-3

हाल ही में मैं देख रहा हूं कि सिंगापूर के प्रीमियर ने एक कार्यक्रम पोस्ट किया था जिसमें उन्होंने फेसबुक पर लिखा था, इसका उल्लेख करने के लिए एक पंक्ति है ..

तर्क केवल "मान और मूल्य" है, मान लें कि आपके पास 0x0FF0 है, फिर, 0FF0 और (F00F + 1), जो 0x0010 के बराबर है, इसका मतलब है कि सबसे कम 1 4 बिट में है .. :)


1
यह सबसे कम बिट को अलग करता है लेकिन आपको इसकी स्थिति नहीं देता है जो यह प्रश्न पूछ रहा है।
रहशिमोतो

मुझे नहीं लगता कि अंतिम बिट खोजने के लिए यह काम करता है।
yyny

मान और ~ मान 0. है
khw

उफ़, मेरी आँखें खराब हो रही हैं। मैंने टिल्ड के लिए माइनस किया। मेरी टिप्पणी की अवहेलना करें
khw

-8

यदि आपके पास संसाधन हैं, तो आप गति में सुधार करने के लिए स्मृति का त्याग कर सकते हैं:

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

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

नोट: यह तालिका कम से कम 4 GB (16 GB) का उपभोग करेगी यदि हम रिटर्न प्रकार को छोड़ देंunsigned ) की । यह दूसरे (निष्पादन गति) के लिए एक सीमित संसाधन (RAM) का व्यापार करने का एक उदाहरण है।

यदि आपके कार्य को पोर्टेबल रहने और किसी भी कीमत पर जितनी तेजी से चलाने की आवश्यकता है, यह जाने का रास्ता होगा। अधिकांश वास्तविक दुनिया के अनुप्रयोगों में, एक 4 जीबी टेबल अवास्तविक है।


1
इनपुट की सीमा पहले से ही पैरामाटर प्रकार द्वारा निर्दिष्ट की गई है - 'अहस्ताक्षरित' एक 32-बिट मान है इसलिए नहीं, आप ठीक नहीं हैं।
ब्रायन

3
उम्म ... क्या आपकी पौराणिक प्रणाली और OS में पृष्ठबद्ध स्मृति की अवधारणा है? कितना समय खर्च हो रहा है?
मिकेग

14
यह एक गैर जवाब है। आपका समाधान सभी वास्तविक दुनिया के अनुप्रयोगों में पूरी तरह से अवास्तविक है और इसे "ट्रेडऑफ़" कहना असंगत है। आपकी पौराणिक प्रणाली जिसमें 16GB RAM है, जो किसी एकल फ़ंक्शन को समर्पित करने के लिए मौजूद नहीं है। आप "क्वांटम कंप्यूटर का उपयोग करें" का उत्तर देने के साथ-साथ होंगे।
ब्रायन

3
गति के लिए बलिदान की स्मृति? एक 4 जीबी + लुकअप टेबल कभी भी किसी भी मौजूदा मशीन पर कैश में फिट नहीं होगी, इसलिए मुझे लगता है कि यह लगभग सभी अन्य उत्तरों की तुलना में शायद धीमी है।

1
अरे। यह भयानक उत्तर मुझे :)परेशान कर रहा है @ दान: आप मेमोरी कैशिंग के बारे में सही हैं। ऊपर मिकगेस की टिप्पणी देखें।
ई। जनम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.