Ayarlanan en az anlamlı bit konumu


121

Bir tamsayıda ayarlanan en önemsiz bitin konumunu belirlemenin verimli bir yolunu arıyorum, örneğin 0x0FF0 için 4 olacaktır.

Önemsiz bir uygulama şudur:

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

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

Bazı döngüleri nasıl sıkıştıracağına dair bir fikrin var mı?

(Not: Bu soru, bu tür şeylerden hoşlanan insanlar içindir, insanların bana xyzoptimization'ın kötü olduğunu söylemesi için değil.)

[değiştir] Fikirler için herkese teşekkürler! Ben de birkaç şey daha öğrendim. Güzel!


while ((değer _N >> (++ konum))! = 0);
Thomas

Yanıtlar:


170

Bit Twiddling Hacks , performans / optimizasyon tartışması eklenmiş mükemmel bir bit twiddling hack koleksiyonu sunar. Sorununuz için en sevdiğim çözüm (o siteden) «çarp ve ara»:

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

Yararlı referanslar:


18
Neden olumsuz oy? Bu, çarpma hızına bağlı olarak muhtemelen en hızlı uygulamadır. Kesinlikle kompakt koddur ve (v & -v) numarası herkesin öğrenmesi ve hatırlaması gereken bir şeydir.
Adam Davis

2
+1 çok güzel, çarpma işlemi if (X&Y) işlemine kıyasla ne kadar pahalı?
Brian R. Bondy

4
Bunun __builtin_ffslveya performansının nasıl olduğunu bilen var mı ffsl?
Steven Lu

2
@Jim Balter, ancak modulo, modern donanımdaki çarpmaya kıyasla çok yavaştır. Bu yüzden buna daha iyi bir çözüm demezdim.
Apriori

2
Bana öyle geliyor ki, hem 0x01 hem de 0x00, diziden 0 değeriyle sonuçlanıyor. Görünüşe göre bu numara 0 geçilirse en düşük bitin ayarlandığını gösterecektir!
abelenky

80

Neden yerleşik ffs'yi kullanmıyorsunuz ? (Linux'tan bir kılavuz sayfası aldım, ancak bundan daha yaygın olarak bulunabilir.)

ffs (3) - Linux kılavuz sayfası

ad

ffs - bir sözcükteki ilk bit kümesini bulur

özet

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

Açıklama

Ffs () işlevi, i sözcüğünde ayarlanan ilk (en az önemli) bitin konumunu döndürür. En önemsiz bit, konum 1 ve en önemli konum, örneğin 32 veya 64'tür. Ffsll () ve ffsl () işlevleri aynı şeyi yapar ancak muhtemelen farklı büyüklükte argümanlar alır.

Geri dönüş değeri

Bu işlevler, ilk bit kümesinin konumunu veya i'de bit ayarlanmadıysa 0'ı döndürür.

Uygun

4.3BSD, POSIX.1-2001.

notlar

BSD sistemlerinin bir prototipi var <string.h>.


6
Bilginize, bu, mevcut olduğunda ilgili montaj komutuna derlenir.
Jérémie

46

bsfBunu yapacak bir x86 montaj talimatı ( ) var. :)

Daha optimize edilmiş mi ?!

Kenar notu:

Bu seviyedeki optimizasyon, doğası gereği mimariye bağlıdır. Günümüz işlemcileri çok karmaşıktır (şube tahmini, önbellek eksiklikleri, ardışık düzenler açısından), hangi kodun hangi mimaride daha hızlı yürütüldüğünü tahmin etmek çok zor. İşlemleri 32'den 9'a düşürmek veya bunun gibi şeyler bazı mimarilerde performansı bile düşürebilir. Tek bir mimaride optimize edilmiş kod, diğerinde daha kötü kodla sonuçlanabilir. Bunu belirli bir CPU için optimize edeceğinizi veya olduğu gibi bırakacağınızı ve derleyicinin neyin daha iyi olduğunu düşündüğünü seçmesine izin vereceğinizi düşünüyorum.


20
@dwc: Anlıyorum ama sanırım şu cümle: "Bazı döngüleri nasıl sıkıştıracağına dair bir fikrin var mı?" böyle bir cevabı tamamen kabul edilebilir kılıyor!
Mehrdad Afshari

5
+1 Onun cevabı, endianness nedeniyle mutlaka mimarisine bağlı, bu yüzden montaj talimatlarına geçmek tamamen geçerli bir cevap.
Chris Lutz

3
+1 Akıllı cevap, evet bu C veya C ++ değil ama iş için doğru araçtır.
Andrew Hare

1
Bekle, boş ver. Tamsayının gerçek değeri burada önemli değil. Afedersiniz.
Chris Lutz

2
@Bastian: İşlenen sıfırsa ZF = 1 ayarlarlar.
Mehrdad Afshari

43

Modern mimarilerin çoğu, en düşük ayarlı bitin veya en yüksek ayarlı bitin konumunu bulmak veya öndeki sıfırların sayısını saymak için bazı talimatlara sahip olacaktır.

Bu sınıfta herhangi bir talimatınız varsa, diğerlerini ucuza taklit edebilirsiniz.

Kağıt üzerinde çalışmak için bir dakikanızı ayırın x & (x-1)ve ( x & ~(x-1) )bunun x'deki en düşük ayarlı biti temizleyeceğini ve mimariye, kelime uzunluğuna vb. Bakılmaksızın sadece en düşük ayarlı biti döndüreceğini fark edin. Bunu bilerek, donanım sayacı kullanmak önemsizdir. -zeroes / en yüksek-set-bit, bunu yapmak için açık bir talimat yoksa, en düşük set bitini bulmak için.

Hiçbir ilgili donanım desteği yoksa, burada verilen önde gelen sıfırların çarpma ve arama uygulaması veya Bit Twiddling Hacks sayfasındakilerden biri, yukarıdaki kimlikleri kullanarak en düşük set bitini verecek şekilde önemsiz bir şekilde dönüştürülebilir ve dalsız olma avantajına sahiptir.


18

Weee, bir sürü çözüm ve görünürde bir kriter değil. Sizler kendinizden utanmalısınız ;-)

Makinem Windows 7 64-bit çalıştıran bir Intel i530 (2.9 GHz). MinGW'nin 32 bitlik bir versiyonuyla derledim.

$ 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

Kodum:

#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
Hem de Bruijn hem de arama için ölçütler yanıltıcı olabilir - böyle sıkı bir döngüde oturmak, ilk işlemden sonra her tür için arama tabloları son döngü sonrasına kadar L1 önbelleğine sabitlenir. Bunun gerçek dünya kullanımıyla eşleşmesi pek olası değildir.
MattW

1
Düşük baytta sıfır olan girişler için, işaretçi atışı nedeniyle kaydırma yerine depolayarak / yeniden yükleyerek daha yüksek baytları alır. (tamamen gereksiz BTW ve bir vardiyanın aksine onu endian bağımlı hale getirir). Her neyse, mikro ölçüt yalnızca sıcak önbellek nedeniyle gerçekçi değil, aynı zamanda dal tahmin edicilerine de sahip ve çok iyi tahmin eden ve LUT'un daha az iş yapmasını sağlayan girdileri test ediyor. Birçok gerçek kullanım durumu, girdilere değil, daha tekdüze bir sonuç dağılımına sahiptir.
Peter Cordes

2
FFS döngünüz ne yazık ki, eski huysuz derleyicinizin engellemediği BSF talimatındaki yanlış bir bağımlılık nedeniyle yavaşladı ( ancak daha yeni gcc, popcnt / lzcnt / tzcnt için de aynı olmalıdır . ÇıkışınaBSF yanlış bir bağımlılık vardır (çünkü gerçek davranış) girdi = 0 çıktıyı değişmeden bırakmaktır). gcc maalesef bunu döngü yinelemeleri arasındaki kaydı temizlemeyerek döngüde taşınan bir bağımlılığa dönüştürür. Bu nedenle döngü, BSF (3) + CMOV'da darboğazla 5 döngüde bir çalışmalıdır. (2) gecikme.
Peter Cordes

1
Karşılaştırma noktanız, LUT'nin FFS yönteminin neredeyse tam olarak iki katına sahip olduğunu buldu, bu da statik analiz tahminime son derece iyi uyuyor :). Döngünüzdeki tek seri bağımlılık, toplamı toplamak olduğu için gecikmeyi değil, veri hızını ölçtüğünüzü unutmayın. Yanlış bağımlılık olmadan, ffs()saat başına bir iş hacmine sahip olmalıydı (3 uop, BSF için 1 ve CMOV için 2 ve farklı bağlantı noktalarında çalışabilirler). Aynı döngü ek yükü ile, saat başına 3 hızda (CPU'nuzda) çalışabilen 7 ALU uop vardır. Tepegöz hakimdir! Kaynak: agner.org/optimize
Peter Cordes

1
Evet, eğer dışı sipariş yürütme döngünün birden tekrarlamalar üst üste olabilir bsf ecx, [ebx+edx*4]ele almadı ecxo beklemek zorunda olduğu bir girdi olarak. (ECX en son bir önceki iteraton'un CMOV'si tarafından yazılmıştır). Ancak CPU, "kaynak sıfırsa hedef değiştirilmemiş bırak" davranışını uygulamak için bu şekilde davranır (bu nedenle, TZCNT için olduğu gibi gerçekten yanlış bir depo değildir; varsayımda dallanma + spekülatif yürütme olmadığı için veri bağımlılığı gereklidir. girdinin sıfır olmaması). ECX'e bağımlılığı kırmak için xor ecx,ecxönüne bir ekleyerek üstesinden gelebiliriz bsf.
Peter Cordes

17

Bunun en hızlı (içsel olmayan / birleştirici olmayan) çözümü, en düşük baytı bulmak ve ardından bu baytı 256 girişli bir arama tablosunda kullanmaktır. Bu size dört koşullu komutun en kötü durum performansını ve en iyi durum olan 1'i verir. Bu yalnızca en az talimat miktarı değil, aynı zamanda modern donanım için çok önemli olan en az sayıda daldır.

Tablonuz (256 adet 8-bit giriş) 0-255 aralığındaki her sayı için LSB indeksini içermelidir. Değerinizin her bir baytını kontrol edersiniz ve sıfır olmayan en düşük baytı bulursunuz, ardından gerçek dizini aramak için bu değeri kullanırsınız.

Bu, 256 bayt bellek gerektirir, ancak bu işlevin hızı o kadar önemliyse, 256 bayt buna değecektir,

Örneğin

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
Aslında bu, üç koşullu olmanın en kötü hali :) Ama evet, bu en hızlı yaklaşımdır (ve genellikle insanların bunun gibi mülakat sorularında aradıkları şey).
Brian

4
Orada bir yerde +8, +16, +24 olmasını istemez misin?
Mark Ransom

7
Herhangi bir arama tablosu, önbelleğin kaybolma olasılığını artırır ve bellek erişimi maliyetine neden olabilir; bu, talimatları yürütmekten birkaç kat daha yüksek olabilir.
Mehrdad Afshari

1
Bit kaydırmaları bile kullanırdım (her seferinde 8 kaydırarak). tamamen kayıtlar kullanılarak yapılabilir. işaretçiler kullanarak hafızaya erişmeniz gerekecektir.
Johannes Schaub -

1
Makul bir çözüm, ancak arama tablosunun önbellekte olmama potansiyeli (belirtildiği gibi çözülebilir) ve dal sayısı (potansiyel dallanma yanlış tahmin) arasında, çok-ve-ara çözümünü tercih ederim (dal yok, daha küçük arama tablosu). Elbette, içsel veya satır içi montajı kullanabiliyorsanız, muhtemelen daha iyi bir seçimdir. Yine de bu çözüm fena değil.

13

OMG bunu yeni sarmaladı.

Bu örneklerin çoğunda eksik olan şey, tüm donanımların nasıl çalıştığı hakkında biraz bilgi sahibi olmaktır.

Ne zaman bir dalınız varsa, CPU hangi dalın alınacağını tahmin etmelidir. Talimat borusu, tahmin edilen yolu gösteren talimatlarla yüklenir. CPU yanlış tahmin ettiyse, talimat borusu temizlenir ve diğer dal yüklenmelidir.

En üstteki basit while döngüsünü düşünün. Tahmin, döngü içinde kalmak olacaktır. Döngüden çıktığında en az bir kez yanlış olacaktır. Bu, talimat borusunu temizleyecektir. Bu davranış, döngüden çıkacağını tahmin etmekten biraz daha iyidir, bu durumda her yinelemede talimat borusunu temizleyecektir.

Kaybedilen CPU döngülerinin miktarı, bir işlemci türünden diğerine büyük ölçüde değişir. Ancak 20 ila 150 kayıp CPU döngüsü bekleyebilirsiniz.

Bir sonraki daha kötü grup, değeri daha küçük parçalara bölerek ve birkaç dal daha ekleyerek birkaç yinelemeden tasarruf edeceğinizi düşündüğünüz yerdir. Bu dalların her biri, talimat borusunu yıkamak için ek bir fırsat ekler ve 20 ila 150 saat döngüsüne mal olur.

Bir tabloda bir değer aradığınızda ne olacağını düşünelim. Muhtemelen, değer şu anda önbellekte değil, en azından işleviniz ilk kez çağrıldığında değil. Bu, değer önbellekten yüklenirken CPU'nun durduğu anlamına gelir. Yine bu, bir makineden diğerine değişir. Yeni Intel yongaları bunu, mevcut iş parçacığı önbellek yükünün tamamlanmasını beklerken iş parçacıklarını değiştirmek için bir fırsat olarak kullanıyor. Bu, bir talimat borusu yıkamasından kolayca daha pahalı olabilir, ancak bu işlemi birkaç kez yapıyorsanız, muhtemelen yalnızca bir kez meydana gelecektir.

Açıkçası, en hızlı sabit zaman çözümü deterministik matematiği içeren çözümdür. Saf ve zarif bir çözüm.

Bu zaten kapsanmışsa özür dilerim.

XCODE AFAIK dışında kullandığım her derleyici, hem ileri bit taraması hem de ters bit taraması için derleyici iç bilgilerine sahiptir. Bunlar, çoğu donanımda, Önbellek Eksikliği, Şube Eksik Tahmin ve diğer programcı tarafından üretilen tökezleme blokları olmadan tek bir montaj talimatına derlenecektir.

Microsoft derleyicileri için _BitScanForward & _BitScanReverse kullanın.
GCC için __builtin_ffs, __builtin_clz, __builtin_ctz kullanın.

Ek olarak, tartışılan konu hakkında yeterince bilginiz yoksa lütfen bir cevap ve potansiyel olarak yanıltıcı yeni gelenler göndermekten kaçının.

Maalesef bir çözüm sunmayı tamamen unuttum .. Bu, görev için montaj seviyesi talimatı olmayan IPAD'de kullandığım kod:

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

Burada anlaşılması gereken şey, pahalı olanın karşılaştırma değil, karşılaştırmadan sonra ortaya çıkan dal olmasıdır. Bu durumda karşılaştırma, .. == 0 ile 0 veya 1 değerine zorlanır ve sonuç, dalın her iki tarafında meydana gelebilecek matematiği birleştirmek için kullanılır.

Düzenle:

Yukarıdaki kod tamamen bozuk. Bu kod çalışır ve hala dalsızdır (optimize edilmişse):

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

0 verilirse -1 döndürür. 0'ı önemsemiyorsanız veya 0 için 31 almaktan memnunsanız, i0 hesaplamasını kaldırarak bir parça zaman tasarrufu yapın.


3
Senin için düzelttim. Gönderdiklerinizi test ettiğinizden emin olun.
Jim Balter

5
İçinde üçlü bir operatör varken, buna nasıl "dalsız" diyebilirsiniz?
BoltBait

2
Koşullu Bir Hareket. Her iki olası değeri parametre olarak alan ve koşullu değerin değerlendirilmesine bağlı olarak bir mov işlemi gerçekleştiren tek bir Assembly dili talimatı. Ve böylece "Şube Ücretsiz" dir. başka bir bilinmeyen veya muhtemelen yanlış adrese atlama yoktur.
Dan


7

Bir set biti aramayı içeren bu benzer gönderiden ilham alarak şunları sunuyoruz:

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

Artıları:

  • döngü yok
  • dallanma yok
  • sabit zamanda çalışır
  • başka türlü sınırların dışında bir sonuç döndürerek değer = 0'ı işler
  • sadece iki satır kod

Eksileri:

  • kodlandığı gibi küçük bir sonsuzluk varsayar (sabitler değiştirilerek düzeltilebilir)
  • double'ın gerçek * 8 IEEE float olduğunu varsayar (IEEE 754)

Güncelleme: Yorumlarda belirtildiği gibi, bir birleşim daha temiz bir uygulamadır (en azından C için) ve şöyle görünür:

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

Bu, her şey için küçük endian depolamalı 32-bit girişleri varsayar (x86 işlemcileri düşünün).


1
İlginç - Bit aritmetik için hala çiftleri kullanmaktan korkuyorum, ancak bunu aklımda tutacağım
Peterchen

Frexp () kullanmak onu biraz daha taşınabilir hale getirebilir
aka. Güzel

1
İşaretçi çevirme ile yazım karıştırma, C veya C ++ 'da güvenli değildir. C ++ 'da memcpy veya C' de bir birleşim kullanın (Veya derleyiciniz güvenli olduğunu garanti ediyorsa C ++ 'da bir birleşim kullanın. Örneğin, C ++' ya yönelik GNU uzantıları (birçok derleyici tarafından desteklenir) birleşim tipinin güvenli olduğunu garanti eder.)
Peter Cordes

1
Daha eski gcc, işaretçi döküm yerine bir birleşimle daha iyi kod üretir: depolama / yeniden yükleme yerine doğrudan bir FP reg (xmm0) 'dan rax'a (movq ile) geçer. Daha yeni gcc ve clang her iki yol için de movq kullanır. Bir birleşim sürümü için godbolt.org/g/x7JBiL adresine bakın . 20'ye kadar aritmetik bir değişiklik yapmanız kasıtlı mı? Kişisel varsayımlar listesi de gerektiği intolduğunu int32_tve bu imzalı sağ shift (o en uygulanması tanımlı C ++) bir aritmetik kaymasıdır
Peter Cordes

1
Ayrıca BTW, Visual Studio (en azından 2013) test / setcc / sub yaklaşımını da kullanır. Ben cmp / adc'yi daha çok seviyorum.
DocMax

5

En kötü durumda 32'den az operasyonla yapılabilir:

Prensip: 2 veya daha fazla biti kontrol etmek, 1 biti kontrol etmek kadar etkilidir.

Örneğin, sizi önce hangi grubun olduğunu kontrol etmekten, sonra o gruptaki her biti en küçükten en büyüğe kontrol etmekten alıkoyan hiçbir şey yoktur.

Yani ...
bir seferde 2 bit kontrol ederseniz, en kötü durumda (Nbit / 2) + 1 çek toplamı elde edersiniz.
Bir seferde 3 biti kontrol ederseniz, en kötü durumda (Nbit / 3) + 2 çek toplamı elde edersiniz.
...

Optimal, 4'lü gruplar halinde kontrol etmektir. Bu, en kötü durumda 32 yerine 11 işlem gerektirir.

En iyi durum, algoritmalarınızın 1 kontrolünden, bu gruplama fikrini kullanırsanız 2 kontrole kadar gider. Ancak en kötü durumda tasarruf için en iyi durumda fazladan 1 çek buna değer.

Not: Bir döngü kullanmak yerine tam olarak yazıyorum çünkü bu şekilde daha verimli.

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

Benden +1. En hızlı değil ama orjinalinden daha hızlı, asıl mesele
Andrew Grant

@ onebyone.livejournal.com: Kodda bir hata olsa bile, gruplama kavramı geçmeye çalıştığım nokta. Gerçek kod örneğinin önemi yoktur ve daha kompakt ancak daha az verimli hale getirilebilir.
Brian R. Bondy

Merak ediyorum da cevabımın gerçekten kötü bir kısmı var mı, yoksa insanlar bunu tam olarak yazmamdan hoşlanmadı mı?
Brian R. Bondy

@ onebyone.livejournal.com: 2 algoritmayı karşılaştırdığınızda, bir optimizasyon aşamasıyla sihirli bir şekilde dönüştürüleceğini varsaymadan, onları oldukları gibi karşılaştırmalısınız. Algoritmamın "daha hızlı" olduğunu da asla iddia etmedim. Sadece daha az işlem olduğu.
Brian R. Bondy

@ onebyone.livejournal.com: ... Daha az işlem olduğunu bilmek için yukarıdaki kodun profilini çıkarmama gerek yok. Bunu açıkça görebiliyorum. Profil oluşturmayı gerektiren hiçbir iddiada bulunmadım.
Brian R. Bondy

4

Neden ikili aramayı kullanmıyorsunuz ? Bu her zaman 5 işlemden sonra tamamlanır (4 baytlık int boyutu varsayılarak):

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 Bu, cevabıma çok benziyor. En iyi durum çalıştırma süresi benim önerimden daha kötü, ancak en kötü durum çalıştırma süresi daha iyidir.
Brian R. Bondy

2

Başka bir yöntem (modül bölme ve arama) burada @ anton-tykhyy tarafından sağlanan aynı bağlantıdan özel olarak bahsedilmeyi hak ediyor . Bu yöntem, performans açısından DeBruijn çarpma ve arama yöntemine çok az ama önemli bir farkla çok benzer.

modül bölünmesi ve arama

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

modül bölme ve arama yöntemi v = 0x00000000 ve v = FFFFFFFF için farklı değerler verirken, DeBruijn çarpma ve arama yöntemi her iki girişte de sıfır döndürür.

Ölçek:-

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
modyavaş. Bunun yerine, orijinal çarpma ve arama yöntemini kullanabilir ve uç durumları işlemek için !vburadan çıkartabilirsiniz r.
Eitan T

3
@EitanT bir optimizer, bu modu bilgisayar korsanlarının zevkinde olduğu gibi hızlı bir çarpmaya dönüştürebilir
phuclv

2

Göre Satranç BitScan sayfasını Programlama çıkarma ve xor ve kendi ölçümlerini hızlı olumsuzladığı daha ve maske olduğunu.

(Sondaki sıfırları sayacaksanız 0, benim sahip olduğum yöntem geri dönerken 63, olumsuzlama ve maske geri döner.0 .)

İşte 64 bitlik bir çıkarma ve 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];

Referans için, olumsuzlama ve maskeleme yönteminin 64 bitlik bir sürümünü burada bulabilirsiniz:

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

Bu (v ^ (v-1))işler sağlanır v != 0. Durumunda ise v == 0ederken bunun 0xFF .... FF döndürür (v & -v)sıfır veren (bu arada yanlış da tampon en azından makul bir sonuca yol açar).
CiaPan

@CiaPan: Bu iyi bir nokta, bundan bahsedeceğim. 63. endekse 0 koyarak bunu çözecek farklı bir De Bruijn sayısı olduğunu tahmin ediyorum.
jnm2

Duh, sorunun olduğu yer burası değil. 0 ve 0x8000000000000000 v ^ (v-1), sonrasında 0xFFFFFFFFFFFFFFFF ile sonuçlanır , bu nedenle onları birbirinden ayırmak yoktur. Benim senaryomda, sıfır asla girilmeyecek.
jnm2

1

Daha düşük dereceli bitlerden herhangi birinin ayarlanıp ayarlanmadığını kontrol edebilirsiniz. Öyleyse, kalan bitlerin alt sırasına bakın. Örneğin,:

32bit int - ilk 16'dan herhangi birinin ayarlanıp ayarlanmadığını kontrol edin. Öyleyse, ilk 8'in herhangi birinin ayarlanıp ayarlanmadığını kontrol edin. Öyleyse, ....

değilse, üst 16'dan herhangi birinin ayarlanıp ayarlanmadığını kontrol edin.

Esasen ikili arama.


1

Bunun tek bir x86 talimatı ile nasıl yapılacağını öğrenmek için cevabıma buradan bakın , tek fark, en az anlamlı küme bitini bulmak için orada anlatılmak BSFyerine ("ileri bit tarama") talimatını isteyeceksiniz BSR.


1

Yine başka bir çözüm, muhtemelen en hızlı değil, ancak oldukça iyi görünüyor.
En azından şubesi yok. ;)

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

tüm almak için 1LSB kullanımı en önemsiz 1'den s ((x & -x) - 1) << 1yerine
phuclv

daha da hızlı bir yol: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;
}

Tüm sayıların% 50'si kodun ilk satırında dönecektir.

Tüm sayıların% 75'i kodun ilk 2 satırında dönecektir.

Tüm sayıların% 87'si kodun ilk 3 satırında dönecektir.

Tüm sayıların% 94'ü kodun ilk 4 satırında dönecektir.

Tüm sayıların% 97'si kodun ilk 5 satırında dönecektir.

vb.

Bence bu kod için en kötü durum senaryosunun ne kadar verimsiz olduğundan şikayet eden insanlar, bu durumun ne kadar nadir olacağını anlamıyorlar.


3
Ve 32 şube yanlış tahmininin en kötü hali :)

1
Bu en azından bir değişime dönüştürülemez mi ...?
Steven Lu

"Bu en azından bir değişime dönüştürülemez mi?" Bunu mümkün olduğunu ima etmeden önce denediniz mi? Ne zamandan beri bir anahtar durumunda hesaplama yapabiliyorsun? Bu bir arama tablosu, sınıf değil.
j riv

1

Bu zekice numarayı, n-bit sayısı için O (log (n)) zamanında yapan "Programlama sanatı, bölüm 4" te "sihirli maskeleri" kullanarak buldum. [log (n) ekstra boşlukla]. Ayar bitini kontrol eden tipik çözümler ya O (n) 'dur ya da bir arama tablosu için O (n) fazladan alana ihtiyaç duyar, bu yüzden bu iyi bir uzlaşmadır.

Sihirli maskeler:

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

Temel fikir: 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 sizin için mevcutsa, bazen bir derleyici görevi sizin için yapabilir :)

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

Sonuç 1 tabanlı dizindir.


1
Akıllıca, ancak girdi bir derleme zamanı sabiti olmadığında felaket derecede kötü derleme derler. godbolt.org/g/7ajMyT . (Gcc ile bitler üzerinde aptal bir döngü veya clang ile gerçek bir özyinelemeli fonksiyon çağrısı.) Gcc / clang ffs(), derleme zamanında değerlendirebilir , bu nedenle sürekli yayılmanın çalışması için bunu kullanmanıza gerek yoktur. (Elbette satır içi asm'den kaçınmanız gerekir.) C ++ 11 olarak çalışan bir şeye gerçekten ihtiyacınız constexprvarsa, yine de GNU C'yi kullanabilirsiniz __builtin_ffs.
Peter Cordes

0

Bu, @ Anton Tykhyy cevabıyla ilgili

İşte benim C ++ 11 constexpr uygulamam, 64 bitlik bir sonucu 32 bit'e düşürerek yayınlamaları ortadan kaldırıyor ve VC ++ 17'deki bir uyarıyı kaldırıyor:

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

Her ikisi de 0 döndüren 0x1 ve 0x0 sorununu çözmek için şunları yapabilirsiniz:

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

ancak derleyici çağrıyı önceden işleyemez veya işlemezse, hesaplamaya birkaç döngü ekleyecektir.

Son olarak, ilgileniyorsanız, kodun amaçlanan şeyi yaptığını kontrol etmek için statik iddiaların bir listesini burada bulabilirsiniz:

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

Günlükleri bulmak biraz maliyetli olsa da işte basit bir alternatif.

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

-3

Geçenlerde Singapur'un galasının facebook'a yazdığı bir programı yayınladığını gördüm, bundan bahsetmek için bir satır var ..

Mantık basitçe "değer & -değer" dir, varsayalım ki 0x0FF0, sonra 0FF0 & (F00F + 1), bu da 0x0010'a eşit, yani en düşük 1 4. bittir .. :)


1
Bu, en düşük biti izole eder, ancak size bu sorunun istediği pozisyonu vermez.
rhashimoto

Bunun son parçayı bulmak için de işe yaradığını sanmıyorum.
yyny

değer & ~ değeri 0'dır.
khw

oops, gözlerim kötüye gidiyor. Tilde yerine eksi aldım.
yorumumu

-8

Eğer siz kaynaklara sahip, sen hızını artırmak amacıyla hafızayı feda edebilirsiniz:

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

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

Not: Bu tablo en az 4 GB tüketir (dönüş türünü olarak bırakırsak 16 GB unsigned). Bu, bir sınırlı kaynağın (RAM) diğeriyle ticaretinin (yürütme hızı) bir örneğidir.

İşlevinizin taşınabilir kalması ve ne pahasına olursa olsun olabildiğince hızlı çalışması gerekiyorsa, gidilecek yol bu olacaktır. Çoğu gerçek dünya uygulamasında, 4GB'lık bir tablo gerçekçi değildir.


1
Giriş aralığı zaten parametre türü tarafından belirtilmiştir - 'işaretsiz' 32 bitlik bir değerdir, bu nedenle hayır, iyi değilsiniz.
Brian

3
umm ... efsanevi sisteminizin ve işletim sisteminizin sayfalı bellek kavramı var mı? Bu ne kadar zamana mal olacak?
Mikeage

14
Bu bir cevap değil. Çözümünüz, TÜM gerçek dünya uygulamalarında tamamen gerçekçi değildir ve buna "değiş tokuş" demek samimiyetsizdir. Tek bir işleve ayırmak için 16 GB RAM'e sahip efsanevi sisteminiz mevcut değil. Sen de "kuantum bilgisayar kullan" cevabını vermiş olurdun.
Brian

3
Hız için hafızadan ödün mü? Bir 4GB + arama tablosu, şu anda mevcut olan herhangi bir makinede asla önbelleğe sığmaz, bu yüzden bunun muhtemelen buradaki diğer tüm yanıtlardan daha yavaş olduğunu hayal ediyorum.

1
Ahh. Bu korkunç cevap beni :)rahatsız etmeye devam ediyor @Dan: Belleği önbelleğe alma konusunda haklısınız. Mikeage'ın yukarıdaki yorumuna bakın.
James
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.