Posisi bit paling tidak signifikan yang ditetapkan


121

Saya mencari cara yang efisien untuk menentukan posisi bit paling tidak signifikan yang diatur dalam bilangan bulat, misalnya untuk 0x0FF0 akan menjadi 4.

Implementasi sepele adalah ini:

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

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

Ada ide bagaimana memeras beberapa siklus darinya?

(Catatan: pertanyaan ini untuk orang-orang yang menikmati hal-hal seperti itu, bukan untuk orang-orang yang mengatakan bahwa optimasi xyz itu jahat.)

[Sunting] Terima kasih semua orang atas idenya! Saya juga telah mempelajari beberapa hal lain. Keren!


sementara ((nilai _N >> (++ pos))! = 0);
Thomas

Jawaban:


170

Bit Twiddling Hacks menawarkan koleksi yang sangat baik dari, er, bit twiddling hacks, dengan diskusi kinerja / pengoptimalan terlampir. Solusi favorit saya untuk masalah Anda (dari situs itu) adalah «perbanyak dan cari»:

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

Referensi yang berguna:


18
Mengapa suara negatif itu? Ini mungkin implementasi tercepat, tergantung pada kecepatan perkalian. Ini tentu saja merupakan kode yang kompak, dan trik (v & -v) adalah sesuatu yang harus dipelajari dan diingat semua orang.
Adam Davis

2
+1 Sangat keren, seberapa mahal operasi perkalian dibandingkan dengan operasi if (X&Y)?
Brian R. Bondy

4
Apakah ada yang tahu bagaimana kinerja ini dibandingkan dengan __builtin_ffslatau ffsl?
Steven Lu

2
@Jim Balter, tetapi modulo sangat lambat dibandingkan dengan perkalian pada perangkat keras modern. Jadi saya tidak akan menyebutnya sebagai solusi yang lebih baik.
Apriori

2
Bagi saya, nilai 0x01 dan 0x00 sama-sama menghasilkan nilai 0 dari array. Rupanya trik ini akan menunjukkan bahwa bit terendah disetel jika 0 dimasukkan!
abelenky

80

Mengapa tidak menggunakan ffs bawaan ? (Saya mengambil halaman manual dari Linux, tetapi lebih banyak tersedia dari itu.)

ffs (3) - Halaman manual Linux

Nama

ffs - temukan kumpulan bit pertama dalam sebuah kata

Ringkasan

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

Deskripsi

Fungsi ffs () mengembalikan posisi bit pertama (paling tidak signifikan) yang ditetapkan dalam kata i. Bit yang paling tidak signifikan adalah posisi 1 dan posisi paling signifikan misalnya 32 atau 64. Fungsi ffsll () dan ffsl () melakukan hal yang sama tetapi mengambil argumen dengan ukuran yang mungkin berbeda.

Nilai Kembali

Fungsi ini mengembalikan posisi kumpulan bit pertama, atau 0 jika tidak ada bit yang disetel di i.

Sesuai dengan

4.3BSD, POSIX.1-2001.

Catatan

Sistem BSD memiliki prototipe dalam format <string.h>.


6
FYI, ini dikompilasi ke perintah assembly yang sesuai jika tersedia.
Jérémie

46

Ada instruksi assembly x86 ( bsf) yang akan melakukannya. :)

Lebih dioptimalkan ?!

Catatan Samping:

Pengoptimalan pada level ini secara inheren bergantung pada arsitektur. Prosesor saat ini terlalu kompleks (dalam hal prediksi cabang, cache miss, pipelining) sehingga sangat sulit untuk memprediksi kode mana yang dieksekusi lebih cepat pada arsitektur mana. Mengurangi operasi dari 32 menjadi 9 atau hal-hal seperti itu bahkan dapat menurunkan kinerja pada beberapa arsitektur. Kode yang dioptimalkan pada satu arsitektur dapat menghasilkan kode yang lebih buruk di arsitektur lain. Saya pikir Anda akan mengoptimalkan ini untuk CPU tertentu atau membiarkannya apa adanya dan membiarkan kompiler memilih apa yang menurutnya lebih baik.


20
@dwc: Saya mengerti, tapi saya pikir klausa ini: "Ada ide bagaimana cara memeras beberapa siklus darinya?" membuat jawaban seperti itu bisa diterima!
Mehrdad Afshari

5
+1 Jawabannya bergantung pada arsitekturnya karena ketekunan, jadi turun ke instruksi perakitan adalah jawaban yang sangat valid.
Chris Lutz

3
+1 Jawaban cerdas, ya ini bukan C atau C ++ tetapi ini adalah alat yang tepat untuk pekerjaan itu.
Andrew Hare

1
Tunggu, lupakan. Nilai sebenarnya dari bilangan bulat tidak menjadi masalah di sini. Maaf.
Chris Lutz

2
@Bastian: Mereka menyetel ZF = 1 jika operannya nol.
Mehrdad Afshari

43

Kebanyakan arsitektur modern memiliki beberapa instruksi untuk menemukan posisi bit set terendah, atau bit set tertinggi, atau menghitung jumlah nol di depan, dll.

Jika Anda memiliki satu instruksi dari kelas ini, Anda dapat dengan murah meniru yang lain.

Luangkan waktu sejenak untuk mengerjakannya di atas kertas dan sadari bahwa itu x & (x-1)akan menghapus bit set terendah dalam x, dan ( x & ~(x-1) )hanya akan mengembalikan bit set terendah, terlepas dari arsitektur, panjang kata, dll. Mengetahui hal ini, sangat mudah menggunakan perangkat keras count-leading -zeroes / tertinggi-set-bit untuk menemukan bit set terendah jika tidak ada instruksi eksplisit untuk melakukannya.

Jika tidak ada dukungan perangkat keras yang relevan sama sekali, implementasi multiply-and-lookup dari count-leading-zero yang diberikan di sini atau salah satu yang ada di halaman Bit Twiddling Hacks dapat dengan mudah dikonversi untuk memberikan bit set terendah menggunakan identitas di atas dan memiliki keuntungan karena tidak memiliki cabang.


18

Weee, banyak solusi dan bukan patokan yang terlihat. Kalian harus malu pada dirimu sendiri ;-)

Mesin saya adalah Intel i530 (2,9 GHz), menjalankan Windows 7 64-bit. Saya mengkompilasi dengan versi 32-bit dari MinGW.

$ 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

Kode saya:

#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
Tolok ukur untuk de Bruijn dan pencarian bisa menyesatkan - duduk dalam lingkaran ketat seperti itu, setelah operasi pertama tabel pencarian untuk setiap jenis akan disematkan dalam cache L1 hingga setelah pengulangan terakhir. Ini sepertinya tidak cocok dengan penggunaan dunia nyata.
MattW

1
Untuk input dengan nol dalam byte rendah, ia mendapatkan byte yang lebih tinggi dengan menyimpan / memuat ulang alih-alih menggeser, karena pointer-cast. (BTW sama sekali tidak perlu, dan membuatnya bergantung pada endian, tidak seperti shift yang tidak). Jadi, microbenchmark tidak hanya tidak realistis karena hot cache, tetapi juga memiliki prediktor cabang yang telah disiapkan dan menguji input yang memprediksi dengan sangat baik dan membuat LUT bekerja lebih sedikit. Banyak kasus penggunaan nyata memiliki distribusi hasil yang lebih seragam, bukan input.
Peter Cordes

2
Sayangnya loop FFS Anda diperlambat oleh ketergantungan palsu dalam instruksi BSF yang tidak dihindari oleh kompilator lama Anda yang keras ( tetapi gcc yang lebih baru seharusnya, sama untuk popcnt / lzcnt / tzcnt . BSFMemiliki ketergantungan palsu pada outputnya (karena perilaku sebenarnya ketika input = 0 adalah membiarkan output tidak berubah). sayangnya gcc mengubahnya menjadi dependensi yang dibawa loop dengan tidak membersihkan register di antara iterasi loop. Jadi loop harus berjalan pada satu per 5 siklus, terhambat pada BSF (3) + CMOV (2) latensi.
Peter Cordes

1
Tolok ukur Anda menemukan bahwa LUT memiliki hampir persis dua kali throughput metode FFS, yang sangat cocok dengan prediksi analisis-statis saya :). Perhatikan bahwa Anda mengukur throughtput, bukan latensi, karena satu-satunya dependensi serial di loop Anda adalah menjumlahkan total. Tanpa ketergantungan palsu, ffs()seharusnya memiliki throughput satu per jam (3 uops, 1 untuk BSF dan 2 untuk CMOV, dan mereka dapat berjalan di port yang berbeda). Dengan overhead loop yang sama, 7 ALU uops yang dapat berjalan (pada CPU Anda) pada 3 jam. Overhead mendominasi! Sumber: agner.org/optimize
Peter Cordes

1
Ya, eksekusi out-of-order bisa tumpang tindih dengan beberapa iterasi loop jika bsf ecx, [ebx+edx*4]tidak diperlakukan ecxsebagai input yang harus ditunggu. (ECX terakhir ditulis oleh CMOV iterasi sebelumnya). Tetapi CPU berperilaku seperti itu, untuk mengimplementasikan perilaku "biarkan dest tidak dimodifikasi jika sumbernya nol" (jadi ini bukan benar-benar dep palsu seperti untuk TZCNT; ketergantungan data diperlukan karena tidak ada eksekusi spekulatif + percabangan pada asumsi bahwa masukannya bukan nol). Kita bisa mengatasinya dengan menambahkan xor ecx,ecxbefore bsf, untuk memutus ketergantungan pada ECX.
Peter Cordes

17

Solusi tercepat (non-intrinsik / non-assembler) untuk ini adalah menemukan byte terendah dan kemudian menggunakan byte itu dalam tabel pencarian 256 entri. Ini memberi Anda kinerja kasus terburuk dari empat instruksi bersyarat dan kasus terbaik 1. Tidak hanya ini jumlah instruksi yang paling sedikit, tetapi juga jumlah cabang yang paling sedikit yang sangat penting pada perangkat keras modern.

Tabel Anda (256 entri 8-bit) harus berisi indeks LSB untuk setiap nomor dalam kisaran 0-255. Anda memeriksa setiap byte dari nilai Anda dan menemukan byte bukan nol terendah, lalu gunakan nilai ini untuk mencari indeks sebenarnya.

Ini memang membutuhkan memori 256-byte, tetapi jika kecepatan fungsi ini sangat penting maka 256-byte itu sangat berharga,

Misalnya

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
Ini sebenarnya kasus terburuk dari tiga persyaratan :) Tapi ya, ini adalah pendekatan tercepat (dan biasanya yang dicari orang dalam pertanyaan wawancara seperti ini).
Brian

4
Tidakkah Anda ingin +8, +16, +24 di sana?
Mark Ransom

7
Setiap tabel pemeta meningkatkan kemungkinan kehilangan cache dan mungkin menimbulkan biaya akses memori yang bisa beberapa kali lipat lebih tinggi daripada menjalankan instruksi.
Mehrdad Afshari

1
saya bahkan akan menggunakan bit-shift (menggeser 8 setiap kali). bisa dilakukan sepenuhnya dengan menggunakan register. menggunakan pointer, Anda harus mengakses memori.
Johannes Schaub - litb

1
Solusi yang masuk akal, tetapi antara potensi tabel pencarian yang tidak berada dalam cache (yang dapat diselesaikan, seperti yang ditunjukkan) dan jumlah cabang (potensi kesalahan prediksi cabang), saya lebih memilih solusi multiply-and-lookup (tidak ada cabang, tabel pemeta yang lebih kecil). Tentu saja, jika Anda dapat menggunakan perakitan intrinsik atau inline, mereka mungkin pilihan yang lebih baik. Tetap saja, solusi ini tidak buruk.

13

OMG baru saja berputar.

Yang kurang dari sebagian besar contoh ini adalah sedikit pemahaman tentang cara kerja semua perangkat keras.

Setiap kali Anda memiliki cabang, CPU harus menebak cabang mana yang akan diambil. Pipa instruksi dimuat dengan instruksi yang mengarah ke jalur yang ditebak. Jika CPU salah menebak maka pipa instruksi akan dibilas, dan cabang lainnya harus dimuat.

Pertimbangkan loop sementara di bagian atas. Tebakannya adalah tetap berada dalam lingkaran. Ini akan salah setidaknya sekali ketika meninggalkan lingkaran. Ini AKAN menyiram pipa instruksi. Perilaku ini sedikit lebih baik daripada menebak bahwa ia akan meninggalkan loop, dalam hal ini ia akan membuang pipa instruksi pada setiap iterasi.

Jumlah siklus CPU yang hilang sangat bervariasi dari satu jenis prosesor ke prosesor berikutnya. Tetapi Anda dapat mengharapkan antara 20 dan 150 siklus CPU yang hilang.

Grup lebih buruk berikutnya adalah di mana Anda berpikir Anda akan menyimpan beberapa iterasi dengan membagi nilai menjadi potongan-potongan yang lebih kecil dan menambahkan beberapa cabang lagi. Masing-masing cabang ini menambahkan peluang tambahan untuk menyiram pipa instruksi dan menghabiskan 20 hingga 150 siklus jam lagi.

Mari kita pertimbangkan apa yang terjadi ketika Anda mencari nilai dalam tabel. Kemungkinan nilainya saat ini tidak ada di cache, setidaknya bukan pertama kali fungsi Anda dipanggil. Artinya, CPU terhenti saat nilainya dimuat dari cache. Sekali lagi ini bervariasi dari satu mesin ke mesin berikutnya. Chip Intel yang baru benar-benar menggunakan ini sebagai kesempatan untuk menukar utas sementara utas saat ini sedang menunggu pemuatan cache selesai. Ini bisa dengan mudah menjadi lebih mahal daripada penyiraman pipa instruksi, namun jika Anda melakukan operasi ini beberapa kali kemungkinan hanya terjadi sekali.

Jelas solusi waktu konstan tercepat adalah yang melibatkan matematika deterministik. Solusi murni dan elegan.

Saya mohon maaf jika ini sudah ditutup.

Setiap kompiler yang saya gunakan, kecuali XCODE AFAIK, memiliki intrinsik kompiler untuk forward bitscan dan reverse bitscan. Ini akan mengkompilasi ke instruksi perakitan tunggal pada sebagian besar perangkat keras tanpa Cache Miss, tanpa Cabang Miss-Prediction dan Tidak ada pemrogram lain yang menghasilkan batu sandungan.

Untuk kompiler Microsoft, gunakan _BitScanForward & _BitScanReverse.
Untuk GCC, gunakan __builtin_ffs, __builtin_clz, __builtin_ctz.

Selain itu, mohon jangan memposting jawaban dan berpotensi menyesatkan pendatang baru jika Anda tidak memiliki cukup pengetahuan tentang subjek yang sedang dibahas.

Maaf saya benar-benar lupa memberikan solusi .. Ini adalah kode yang saya gunakan di iPad yang tidak memiliki instruksi tingkat perakitan untuk tugas tersebut:

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

Hal yang perlu dipahami di sini adalah bahwa bukan pembanding yang mahal, tetapi cabang yang muncul setelah pembandingan. Perbandingan dalam kasus ini dipaksa menjadi nilai 0 atau 1 dengan .. == 0, dan hasilnya digunakan untuk menggabungkan matematika yang akan terjadi di kedua sisi cabang.

Edit:

Kode di atas rusak total. Kode ini berfungsi dan masih bebas cabang (jika dioptimalkan):

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

Ini mengembalikan -1 jika diberikan 0. Jika Anda tidak peduli tentang 0 atau senang mendapatkan 31 untuk 0, hapus kalkulasi i0, menghemat waktu.


3
Saya memperbaikinya untuk Anda. Pastikan untuk menguji apa yang Anda posting.
Jim Balter

5
Bagaimana Anda bisa menyebutnya "bebas cabang" jika ada operator terner di sana?
BoltBait

2
Ini adalah Langkah Bersyarat. Sebuah instruksi bahasa assembly yang mengambil kedua nilai yang mungkin sebagai parameter, dan melakukan operasi mov berdasarkan evaluasi kondisional. Dan dengan demikian disebut "Bebas Cabang". tidak ada lompatan ke alamat lain yang tidak diketahui atau mungkin salah.
Dan

FWIW gcc membuat cabang bahkan di -O3 godbolt.org/z/gcsUHd
Qix - MONICA SALAH

7

Terinspirasi oleh posting serupa ini yang melibatkan pencarian sedikit, saya menawarkan yang berikut:

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

Kelebihan:

  • tidak ada loop
  • tidak ada percabangan
  • berjalan dalam waktu yang konstan
  • menangani nilai = 0 dengan mengembalikan hasil di luar batas
  • hanya dua baris kode

Kekurangan:

  • mengasumsikan sedikit ketangguhan sebagai kode (dapat diperbaiki dengan mengubah konstanta)
  • mengasumsikan bahwa double adalah float IEEE * 8 nyata (IEEE 754)

Pembaruan: Seperti yang ditunjukkan di komentar, serikat pekerja adalah implementasi yang lebih bersih (untuk C, setidaknya) dan akan terlihat seperti:

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

Ini mengasumsikan int 32-bit dengan penyimpanan little-endian untuk semuanya (pikirkan prosesor x86).


1
Menarik - Saya masih takut menggunakan nomor ganda untuk aritmatika bit, tetapi saya akan mengingatnya
peterchen

Menggunakan frexp () mungkin membuatnya sedikit lebih portabel
alias bagus

1
Jenis-punning dengan pointer-casting tidak aman di C atau C ++. Gunakan memcpy di C ++, atau union di C. (Atau union di C ++ jika compiler Anda menjamin keamanannya. Misalnya, ekstensi GNU ke C ++ (didukung oleh banyak compiler) menjamin union type-punning aman.)
Peter Cordes

1
Gcc yang lebih lama juga membuat kode yang lebih baik dengan gabungan daripada pointer-cast: Gcc berpindah langsung dari reg FP (xmm0) ke rax (dengan movq) daripada menyimpan / memuat ulang. Gcc dan clang yang lebih baru menggunakan movq untuk kedua cara tersebut. Lihat godbolt.org/g/x7JBiL untuk versi gabungan. Apakah Anda sengaja melakukan pergeseran aritmatika sebesar 20? Asumsi Anda juga harus daftar itu intadalah int32_t, dan bahwa pergeseran kanan menandatangani adalah pergeseran aritmatika (di C ++ itu pelaksanaan yang ditetapkan)
Peter Cordes

1
Juga BTW, Visual Studio (2013 setidaknya) juga menggunakan pendekatan test / setcc / sub. Saya sendiri lebih suka cmp / adc.
DocMax

5

Ini dapat dilakukan dengan kasus terburuk kurang dari 32 operasi:

Prinsip: Memeriksa 2 bit atau lebih sama efisiennya dengan memeriksa 1 bit.

Jadi misalnya tidak ada yang menghentikan Anda untuk memeriksa pengelompokan mana yang pertama, kemudian memeriksa setiap bit dari yang terkecil hingga terbesar dalam kelompok itu.

Jadi ...
jika Anda memeriksa 2 bit sekaligus, Anda memiliki kasus terburuk (Nbits / 2) + 1 total pemeriksaan.
jika Anda memeriksa 3 bit pada satu waktu yang Anda miliki dalam kasus terburuk (Nbits / 3) + 2 pemeriksaan total.
...

Optimal akan memeriksa dalam kelompok 4. Yang akan membutuhkan dalam kasus terburuk 11 operasi alih-alih 32 Anda.

Kasus terbaik beralih dari 1 pemeriksaan algoritme Anda ke 2 pemeriksaan jika Anda menggunakan ide pengelompokan ini. Tetapi 1 cek ekstra dalam kasus terbaik itu sepadan untuk penghematan kasus terburuk.

Catatan: Saya menuliskannya secara penuh daripada menggunakan loop karena cara itu lebih efisien.

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 dari saya. Ini bukan yang tercepat tapi lebih cepat dari aslinya, itulah intinya ...
Andrew Grant

@ onebyone.livejournal.com: Meskipun ada bug dalam kode, konsep pengelompokan adalah poin yang saya coba sampaikan. Sampel kode sebenarnya tidak terlalu penting, dan dapat dibuat lebih ringkas tetapi kurang efisien.
Brian R. Bondy

Saya hanya ingin tahu apakah ada bagian yang benar-benar buruk dari jawaban saya, atau jika orang tidak begitu saja saya menuliskannya secara lengkap?
Brian R. Bondy

@ onebyone.livejournal.com: Saat Anda membandingkan 2 algoritme, Anda harus membandingkannya sebagaimana adanya, tidak berasumsi bahwa satu algoritme akan diubah secara ajaib oleh fase pengoptimalan. Saya juga tidak pernah mengklaim algoritme saya "lebih cepat". Hanya saja itu kurang operasi.
Brian R. Bondy

@ onebyone.livejournal.com: ... Saya tidak perlu membuat profil kode di atas untuk mengetahui bahwa ini kurang operasi. Saya bisa melihatnya dengan jelas. Saya tidak pernah membuat klaim yang membutuhkan pembuatan profil.
Brian R. Bondy

4

Mengapa tidak menggunakan pencarian biner ? Ini akan selalu selesai setelah 5 operasi (dengan asumsi ukuran int 4 byte):

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

+1 Ini sangat mirip dengan jawaban saya. Waktu pengoperasian kasus terbaik lebih buruk dari saran saya, tetapi waktu pengoperasian kasus terburuk lebih baik.
Brian R. Bondy

2

Metode lain (pembagian modulus dan pencarian) layak mendapat perhatian khusus di sini dari tautan yang sama yang disediakan oleh @ anton-tykhyy. Metode ini sangat mirip dalam performanya dengan metode penggandaan dan pencarian DeBruijn dengan sedikit perbedaan namun penting.

divisi modulus dan pencarian

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

pembagian modulus dan metode pencarian mengembalikan nilai yang berbeda untuk v = 0x00000000 dan v = FFFFFFFF sedangkan metode perkalian dan pencarian DeBruijn mengembalikan nol pada kedua input.

uji:-

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
modlambat. Sebagai gantinya, Anda dapat menggunakan metode perkalian-dan-pencarian asli dan kurangi !vdari runtuk menangani kasus tepi.
Eitan T

3
@EitanT, seorang pengoptimal dapat mengubah mod itu menjadi perkalian cepat seperti dalam kesenangan peretas
phuclv

2

Menurut halaman BitScan Pemrograman Catur dan pengukuran saya sendiri, kurangi dan xor lebih cepat daripada negate dan mask.

(Perhatikan daripada jika Anda akan menghitung nol di belakangnya 0, metode yang saya miliki mengembalikannya 63sedangkan negate dan mask kembali 0.)

Berikut adalah pengurangan 64-bit dan 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];

Untuk referensi, berikut adalah versi 64-bit dari metode negate and mask:

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

Ini (v ^ (v-1))bekerja disediakan v != 0. Dalam kasus v == 0mengembalikan 0xFF .... FF sementara (v & -v)memberikan nol (yang omong-omong salah, juga, buf setidaknya itu mengarah ke hasil yang wajar).
CiaPan

@CiaPan: Itu poin yang bagus, saya akan menyebutkannya. Saya menduga ada angka De Bruijn yang berbeda yang akan menyelesaikan masalah ini dengan meletakkan 0 di indeks ke-63.
jnm2

Duh, bukan itu masalahnya. 0 dan 0x8000000000000000 keduanya menghasilkan 0xFFFFFFFFFFFFFFFF setelahnya v ^ (v-1), jadi tidak ada yang membedakan keduanya. Dalam skenario saya, nol tidak akan pernah menjadi masukan.
jnm2

1

Anda dapat memeriksa apakah ada bit urutan bawah yang disetel. Jika demikian maka lihat urutan bawah dari bit yang tersisa. misalnya,:

32bit int - periksa apakah salah satu dari 16 pertama disetel. Jika demikian, periksa apakah salah satu dari 8 yang pertama telah disetel. jika begitu, ....

jika tidak, periksa apakah salah satu dari 16 di atas sudah diatur ..

Pada dasarnya ini adalah pencarian biner.


1

Lihat jawaban saya di sini untuk mengetahui cara melakukannya dengan satu instruksi x86, kecuali bahwa untuk menemukan bit set yang paling tidak signifikan, Anda akan menginginkan BSFinstruksi ("bit scan forward") daripada BSRdijelaskan di sana.


1

Namun solusi lain, mungkin bukan yang tercepat, tetapi tampaknya cukup bagus.
Setidaknya tidak memiliki cabang. ;)

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

untuk mendapatkan semua 1dari 1 yang paling tidak signifikan hingga LSB, gunakan ((x & -x) - 1) << 1sebagai gantinya
phuclv

cara yang lebih cepat: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% dari semua nomor akan ditampilkan di baris pertama kode.

75% dari semua angka akan kembali pada 2 baris kode pertama.

87% dari semua angka akan kembali dalam 3 baris kode pertama.

94% dari semua angka akan kembali dalam 4 baris kode pertama.

97% dari semua angka akan kembali dalam 5 baris kode pertama.

dll.

Saya pikir orang-orang yang mengeluh tentang betapa tidak efisiennya skenario kasus terburuk untuk kode ini tidak memahami betapa langka kondisi itu akan terjadi.


3
Dan kasus terburuk dari 32

1
Tidak bisakah ini setidaknya dibuat menjadi sebuah saklar ...?
Steven Lu

"Tidak bisakah ini setidaknya dijadikan saklar ...?" Apakah Anda mencoba melakukan itu sebelum menyiratkan bahwa itu mungkin? Sejak kapan Anda dapat melakukan perhitungan tepat pada kasus sakelar? Ini adalah tabel pemeta, bukan kelas.
j riv

1

Menemukan trik pintar ini menggunakan 'topeng ajaib' dalam "Seni pemrograman, bagian 4", yang melakukannya dalam waktu O (log (n)) untuk bilangan n-bit. [dengan log (n) spasi ekstra]. Solusi khas yang memeriksa bit set adalah O (n) atau membutuhkan O (n) ruang ekstra untuk tabel pencarian, jadi ini adalah kompromi yang baik.

Masker ajaib:

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

Ide kunci: Tidak ada angka nol di belakang di 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

Jika C ++ 11 tersedia untuk Anda, terkadang kompiler dapat melakukan tugas tersebut untuk Anda :)

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

Hasilnya adalah indeks berbasis 1.


1
Pintar, tetapi dapat dikompilasi menjadi perakitan yang sangat buruk jika inputnya bukan konstanta waktu kompilasi. godbolt.org/g/7ajMyT . (Perulangan bodoh pada bit dengan gcc, atau panggilan fungsi rekursif aktual dengan clang.) Gcc / clang dapat dievaluasi ffs()pada waktu kompilasi, jadi Anda tidak perlu menggunakan ini agar propagasi konstan berfungsi. (Anda harus menghindari inline-asm, tentu saja.) Jika Anda benar-benar membutuhkan sesuatu yang bekerja sebagai C ++ 11 constexpr, Anda masih dapat menggunakan GNU C __builtin_ffs.
Peter Cordes

0

Ini sehubungan dengan jawaban @Anton Tykhyy

Berikut adalah implementasi constexpr C ++ 11 saya menghilangkan gips dan menghapus peringatan pada VC ++ 17 dengan memotong hasil 64bit menjadi 32 bit:

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

Untuk mengatasi masalah 0x1 dan 0x0, keduanya mengembalikan 0, Anda dapat melakukan:

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

tetapi jika kompilator tidak dapat atau tidak mau melakukan praproses, panggilan itu akan menambahkan beberapa siklus ke kalkulasi.

Terakhir, jika tertarik, berikut daftar statik yang menegaskan untuk memeriksa bahwa kode melakukan apa yang dimaksudkan untuk:

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

Berikut ini satu alternatif sederhana, meskipun mencari log agak mahal.

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

-3

baru-baru ini saya melihat bahwa perdana menteri singapura memposting program yang dia tulis di facebook, ada satu baris untuk menyebutkannya ..

Logikanya hanyalah "nilai & -nilai", misalkan Anda memiliki 0x0FF0, lalu, 0FF0 & (F00F + 1), yang sama dengan 0x0010, itu berarti 1 terendah ada di bit ke-4 .. :)


1
Ini mengisolasi bit terendah tetapi tidak memberi Anda posisinya yang ditanyakan oleh pertanyaan ini.
rhashimoto

Saya tidak berpikir ini berfungsi untuk menemukan bit terakhir.
yyny

nilai & ~ nilai 0.
khw

Ups, mataku jadi buruk. Saya salah mengira minus untuk tilde. abaikan komentar saya
khw

-8

Jika Anda memiliki sumber daya, Anda dapat mengorbankan memori untuk meningkatkan kecepatan:

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

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

Catatan: Tabel ini akan menghabiskan setidaknya 4 GB (16 GB jika kita membiarkan tipe pengembalian sebagai unsigned). Ini adalah contoh perdagangan satu sumber daya terbatas (RAM) dengan yang lain (kecepatan eksekusi).

Jika fungsi Anda perlu tetap portabel dan berjalan secepat mungkin dengan biaya berapa pun, ini adalah cara yang tepat. Di sebagian besar aplikasi dunia nyata, tabel 4GB tidak realistis.


1
Kisaran input sudah ditentukan oleh jenis parameter - 'unsigned' adalah nilai 32-bit jadi tidak, Anda tidak baik-baik saja.
Brian

3
umm ... apakah sistem mitos dan OS Anda memiliki konsep paged memory? Berapa lama waktu yang dibutuhkan?
Mikeage

14
Ini bukan jawaban. Solusi Anda sama sekali tidak realistis dalam SEMUA aplikasi dunia nyata dan menyebutnya sebagai "tradeoff" adalah tidak jujur. Sistem mistis Anda yang memiliki RAM 16GB untuk digunakan untuk satu fungsi tidak ada. Anda akan menjawab "gunakan komputer kuantum".
Brian

3
Mengorbankan memori untuk kecepatan? Tabel pencarian 4GB + tidak akan pernah cocok dengan cache pada mesin yang ada saat ini, jadi saya membayangkan ini mungkin lebih lambat daripada hampir semua jawaban lain di sini.

1
Argh. Jawaban mengerikan ini terus menghantui saya :)@Dan: Anda benar tentang cache memori. Lihat komentar Mikeage di atas.
e. James
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.