Vị trí của bit ít quan trọng nhất được đặt


120

Tôi đang tìm một cách hiệu quả để xác định vị trí của bit quan trọng nhất được đặt trong một số nguyên, ví dụ: đối với 0x0FF0, nó sẽ là 4.

Một triển khai tầm thường là:

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

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

Bất kỳ ý tưởng làm thế nào để vắt một số chu kỳ ra khỏi nó?

(Lưu ý: câu hỏi này dành cho những người thích những thứ như vậy, không phải để mọi người nói với tôi xyzoptimization là xấu xa.)

[sửa] Cảm ơn mọi người vì những ý tưởng! Tôi cũng đã học được một số điều khác. Mát mẻ!


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

Câu trả lời:


170

Bit Twiddling Hacks cung cấp một bộ sưu tập tuyệt vời các bản hack twiddling bit, có đính kèm thảo luận về hiệu suất / tối ưu hóa. Giải pháp yêu thích của tôi cho vấn đề của bạn (từ trang web đó) là «nhân lên và tra cứu»:

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

Tham khảo hữu ích:


18
Tại sao lại ủng hộ? Đây có thể là cách thực hiện nhanh nhất, tùy thuộc vào tốc độ của phép nhân. Nó chắc chắn là mã nhỏ gọn và thủ thuật (v & -v) là điều mà mọi người nên học và ghi nhớ.
Adam Davis

2
+1 rất tuyệt, một phép toán nhân đắt hơn như thế nào so với phép toán if (X&Y)?
Brian R. Bondy

4
Có ai biết làm thế nào hiệu suất của điều này so với __builtin_ffslhoặc ffsl?
Steven Lu

2
@Jim Balter, nhưng modulo rất chậm so với phép nhân trên phần cứng hiện đại. Vì vậy, tôi sẽ không gọi nó là một giải pháp tốt hơn.
Apriori

2
Đối với tôi, dường như cả giá trị 0x01 và 0x00 đều dẫn đến giá trị 0 từ mảng. Rõ ràng thủ thuật này sẽ chỉ ra rằng bit thấp nhất được đặt nếu 0 được chuyển vào!
abelenky

80

Tại sao không sử dụng ffs tích hợp sẵn ? (Tôi đã lấy một trang người đàn ông từ Linux, nhưng nó có sẵn rộng rãi hơn thế.)

ffs (3) - Trang người dùng Linux

Tên

ffs - tìm bit đầu tiên được đặt trong một từ

Tóm tắc

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

Sự miêu tả

Hàm ffs () trả về vị trí của tập bit đầu tiên (ít quan trọng nhất) trong từ i. Bit ít quan trọng nhất là vị trí 1 và vị trí quan trọng nhất, ví dụ 32 hoặc 64. Các hàm ffsll () và ffsl () thực hiện tương tự nhưng lấy các đối số có thể có kích thước khác nhau.

Giá trị trả lại

Các hàm này trả về vị trí của tập bit đầu tiên, hoặc 0 nếu không có bit nào được đặt trong i.

Tuân theo

4.3BSD, POSIX.1-2001.

Ghi chú

Hệ thống BSD có một nguyên mẫu trong <string.h>.


6
FYI, điều này được biên dịch thành lệnh hợp ngữ tương ứng khi có sẵn.
Jérémie

46

Có một lệnh hợp ngữ x86 ( bsf) sẽ thực hiện điều đó. :)

Tối ưu hóa hơn ?!

Ghi chú bên lề:

Tối ưu hóa ở cấp độ này vốn dĩ phụ thuộc vào kiến ​​trúc. Các bộ vi xử lý ngày nay quá phức tạp (về dự đoán rẽ nhánh, bỏ sót bộ nhớ cache, ghép nối) nên rất khó để dự đoán mã nào được thực thi nhanh hơn trên kiến ​​trúc nào. Giảm hoạt động từ 32 xuống 9 hoặc những thứ tương tự thậm chí có thể làm giảm hiệu suất trên một số kiến ​​trúc. Mã được tối ưu hóa trên một cấu trúc có thể dẫn đến mã kém hơn trong cấu trúc khác. Tôi nghĩ bạn có thể tối ưu hóa điều này cho một CPU cụ thể hoặc để nguyên như vậy và để trình biên dịch chọn những gì nó cho là tốt hơn.


20
@dwc: Tôi hiểu, nhưng tôi nghĩ mệnh đề này: "Bất kỳ ý tưởng nào về cách vắt một số chu kỳ ra khỏi nó?" làm cho một câu trả lời như vậy hoàn toàn có thể chấp nhận được!
Mehrdad Afshari

5
+1 Câu trả lời của anh ấy nhất thiết phải phụ thuộc vào kiến ​​trúc của anh ấy vì sự bền bỉ, vì vậy việc thả xuống hướng dẫn lắp ráp là một câu trả lời hoàn toàn hợp lệ.
Chris Lutz

3
+1 Câu trả lời thông minh, có, nó không phải là C hoặc C ++ nhưng nó là công cụ phù hợp cho công việc.
Andrew Hare

1
Chờ đã, đừng bận tâm. Giá trị thực của số nguyên không quan trọng ở đây. Lấy làm tiếc.
Chris Lutz

2
@Bastian: Họ đặt ZF = 1 nếu toán hạng bằng 0.
Mehrdad Afshari

43

Hầu hết các kiến ​​trúc hiện đại sẽ có một số hướng dẫn để tìm vị trí của bit đặt thấp nhất, hoặc bit đặt cao nhất, hoặc đếm số lượng các số 0 đứng đầu, v.v.

Nếu bạn có bất kỳ hướng dẫn nào của lớp này, bạn có thể bắt chước các hướng dẫn khác một cách rẻ tiền.

Hãy dành một chút thời gian để làm việc trên giấy và nhận ra rằng điều đó x & (x-1)sẽ xóa bit đặt thấp nhất trong x và ( x & ~(x-1) )sẽ chỉ trả lại bit đặt thấp nhất, bất kể độ dài từ, v.v ... Biết được điều này, việc sử dụng phần cứng dẫn đầu là điều tầm thường -zeroes / cao nhất-set-bit để tìm bit đặt thấp nhất nếu không có hướng dẫn rõ ràng để làm như vậy.

Nếu hoàn toàn không có hỗ trợ phần cứng liên quan, việc triển khai nhân và tra cứu các số 0 đứng đầu được đưa ra ở đây hoặc một trong các số trên trang Bit Twiddling Hacks có thể được chuyển đổi một cách đáng kể để tạo ra bit thấp nhất bằng cách sử dụng các nhận dạng trên và có ưu điểm là không phân nhánh.


18

Chúng tôi thấy, vô số giải pháp và không phải là một điểm chuẩn trong tầm mắt. Mọi người nên tự xấu hổ về mình ;-)

Máy của tôi là Intel i530 (2,9 GHz), chạy Windows 7 64-bit. Tôi đã biên dịch với phiên bản 32-bit của 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

Mã của tôi:

#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
Các điểm chuẩn cho cả de Bruijn và tra cứu có thể gây hiểu lầm - ngồi trong một vòng lặp chặt chẽ như vậy, sau thao tác đầu tiên, các bảng tra cứu cho mỗi loại sẽ được ghim trong bộ đệm L1 cho đến sau vòng lặp cuối cùng. Điều này có thể không phù hợp với việc sử dụng trong thế giới thực.
MattW

1
Đối với các đầu vào có số 0 ở byte thấp, nó sẽ nhận được các byte cao hơn bằng cách lưu trữ / tải lại thay vì dịch chuyển, do con trỏ-ép kiểu. (BTW hoàn toàn không cần thiết và làm cho nó phụ thuộc vào endian, không giống như một sự thay đổi sẽ không). Dù sao, vì vậy không chỉ microbenchmark không thực tế vì bộ nhớ đệm nóng, nó còn có các bộ dự đoán nhánh được cài sẵn và kiểm tra các đầu vào dự đoán rất tốt và làm cho LUT hoạt động ít hơn. Nhiều trường hợp sử dụng thực có sự phân phối kết quả đồng đều hơn, không phải đầu vào.
Peter Cordes

2
Vòng lặp FFS của bạn không may bị chậm lại do sự phụ thuộc sai trong lệnh BSF mà trình biên dịch cũ nát của bạn không tránh được ( nhưng gcc mới hơn cũng nên làm như vậy đối với popcnt / lzcnt / tzcnt . BSFCó phụ thuộc sai vào đầu ra của nó (vì hành vi thực tế khi đầu vào = 0 là để đầu ra không thay đổi). gcc không may biến điều này thành một phụ thuộc được thực hiện trong vòng lặp bằng cách không xóa thanh ghi giữa các lần lặp vòng lặp. Vì vậy, vòng lặp sẽ chạy ở một trên 5 chu kỳ, bị tắc nghẽn trên BSF (3) + CMOV (2) độ trễ.
Peter Cordes

1
Điểm chuẩn của bạn cho thấy rằng LUT có thông lượng gần như chính xác gấp đôi so với phương pháp FFS, phù hợp với dự đoán phân tích tĩnh của tôi cực kỳ tốt :). Lưu ý rằng bạn đang đo thông lượng, chứ không phải độ trễ, bởi vì sự phụ thuộc nối tiếp duy nhất trong vòng lặp của bạn là tính tổng. Nếu không có sự phụ thuộc sai, đáng ffs()lẽ phải có thông lượng là một cho mỗi đồng hồ (3 uops, 1 cho BSF và 2 cho CMOV, và chúng có thể chạy trên các cổng khác nhau). Với cùng một chi phí vòng lặp, đó là 7 ALU uops có thể chạy (trên CPU của bạn) với tốc độ 3 mỗi xung nhịp. Overhead thống trị! Nguồn: agner.org/optimize
Peter Cordes

1
Có, việc thực thi không theo thứ tự có thể chồng chéo nhiều lần lặp lại của vòng lặp nếu bsf ecx, [ebx+edx*4]không được coi ecxlà đầu vào mà nó phải đợi. (ECX được viết lần cuối bởi CMOV của người lặp trước). Nhưng CPU hoạt động theo cách đó, để thực hiện hành vi "để lại không sửa đổi nếu nguồn bằng không" (vì vậy nó không thực sự là một dep giả như đối với TZCNT; cần có sự phụ thuộc dữ liệu vì không có sự phân nhánh + thực thi suy đoán trên giả định rằng đầu vào là khác 0). Chúng tôi có thể khắc phục bằng cách thêm dấu xor ecx,ecxtrước dấu bsf, để phá vỡ sự phụ thuộc vào ECX.
Peter Cordes

17

Giải pháp nhanh nhất (không nội tại / không hợp ngữ) cho điều này là tìm byte thấp nhất và sau đó sử dụng byte đó trong bảng tra cứu 256 mục nhập. Điều này mang lại cho bạn hiệu suất trong trường hợp xấu nhất là bốn lệnh có điều kiện và trường hợp tốt nhất là 1. Đây không chỉ là số lượng lệnh ít nhất mà còn là số nhánh ít nhất, điều này cực kỳ quan trọng trên phần cứng hiện đại.

Bảng của bạn (256 mục nhập 8-bit) phải chứa chỉ mục của LSB cho mỗi số trong phạm vi 0-255. Bạn kiểm tra từng byte giá trị của mình và tìm byte khác 0 thấp nhất, sau đó sử dụng giá trị này để tra cứu chỉ mục thực.

Điều này yêu cầu 256 byte bộ nhớ, nhưng nếu tốc độ của chức năng này quan trọng như vậy thì 256 byte cũng xứng đáng,

Ví dụ

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
Nó thực sự là trường hợp xấu nhất trong ba điều kiện :) Nhưng có, đây là cách tiếp cận nhanh nhất (và thường là những gì mọi người tìm kiếm trong các câu hỏi phỏng vấn như thế này).
Brian

4
Bạn không muốn có +8, +16, +24 ở đâu đó?
Đánh dấu tiền chuộc

7
Bất kỳ bảng tra cứu nào cũng làm tăng khả năng bỏ sót bộ nhớ cache và có thể phải chịu chi phí truy cập bộ nhớ có thể cao hơn vài bậc so với việc thực hiện các lệnh.
Mehrdad Afshari

1
tôi thậm chí sẽ sử dụng dịch chuyển bit (dịch chuyển nó 8 lần mỗi lần). có thể được thực hiện hoàn toàn bằng cách sử dụng các thanh ghi sau đó. sử dụng con trỏ, bạn sẽ phải truy cập bộ nhớ.
Johannes Schaub - litb

1
Giải pháp hợp lý, nhưng giữa khả năng bảng tra cứu không nằm trong bộ nhớ cache (có thể được giải quyết, như đã chỉ ra) và số lượng nhánh (khả năng nhầm nhánh), tôi thích giải pháp nhân và tra cứu hơn (không có nhánh, bảng tra cứu nhỏ hơn). Tất nhiên, nếu bạn có thể sử dụng bản chất hoặc lắp ráp nội tuyến, chúng có lẽ là lựa chọn tốt hơn. Tuy nhiên, giải pháp này không tệ.

13

OMG đã biến điều này thành hình xoắn ốc.

Điều mà hầu hết các ví dụ này còn thiếu là một chút hiểu biết về cách hoạt động của tất cả phần cứng.

Bất cứ khi nào bạn có một nhánh, CPU phải đoán nhánh nào sẽ được sử dụng. Đường ống hướng dẫn được tải với các hướng dẫn dẫn xuống đường dẫn được đoán. Nếu CPU đã đoán sai thì đường dẫn lệnh sẽ bị xả và nhánh khác phải được tải.

Hãy xem xét vòng lặp while đơn giản ở trên cùng. Dự đoán sẽ nằm trong vòng lặp. Nó sẽ sai ít nhất một lần khi nó rời khỏi vòng lặp. Điều này SẼ làm trôi đường ống hướng dẫn. Hành vi này tốt hơn một chút so với việc đoán rằng nó sẽ rời khỏi vòng lặp, trong trường hợp đó, nó sẽ tuôn ra đường ống hướng dẫn trên mỗi lần lặp.

Số lượng chu kỳ CPU bị mất rất khác nhau giữa các loại vi xử lý. Nhưng bạn có thể mong đợi từ 20 đến 150 chu kỳ CPU bị mất.

Nhóm tồi tệ hơn tiếp theo là nơi bạn nghĩ rằng bạn sẽ tiết kiệm một vài lần lặp lại bằng cách chia nhỏ giá trị thành các phần nhỏ hơn và thêm một số nhánh khác. Mỗi nhánh này tạo thêm một cơ hội để xả đường ống hướng dẫn và tốn thêm 20 đến 150 chu kỳ đồng hồ.

Hãy xem xét điều gì sẽ xảy ra khi bạn tra cứu một giá trị trong bảng. Rất có thể giá trị hiện không có trong bộ nhớ cache, ít nhất không phải là lần đầu tiên hàm của bạn được gọi. Điều này có nghĩa là CPU bị đình trệ trong khi giá trị được tải từ bộ nhớ cache. Một lần nữa điều này thay đổi từ máy này sang máy tiếp theo. Các chip Intel mới thực sự sử dụng điều này như một cơ hội để hoán đổi các luồng trong khi luồng hiện tại đang đợi quá trình tải bộ đệm hoàn tất. Điều này có thể dễ dàng tốn kém hơn so với xả nước theo đường ống hướng dẫn, tuy nhiên nếu bạn thực hiện thao tác này nhiều lần thì nó có thể chỉ xảy ra một lần.

Rõ ràng là giải pháp thời gian không đổi nhanh nhất là giải pháp liên quan đến toán học xác định. Một giải pháp tinh khiết và thanh lịch.

Tôi xin lỗi nếu điều này đã được bảo hiểm.

Mọi trình biên dịch mà tôi sử dụng, ngoại trừ XCODE AFAIK, đều có bản chất của trình biên dịch cho cả quét bit thuận và quét bit ngược. Chúng sẽ biên dịch thành một lệnh hợp ngữ duy nhất trên hầu hết phần cứng mà không có Cache Miss, không có dự đoán rẽ nhánh và không có lập trình viên nào khác tạo ra các trở ngại.

Đối với trình biên dịch của Microsoft, hãy sử dụng _BitScanForward & _BitScanReverse.
Đối với GCC, hãy sử dụng __builtin_ffs, __builtin_clz, __builtin_ctz.

Ngoài ra, vui lòng không đăng câu trả lời và có khả năng gây hiểu lầm cho những người mới đến nếu bạn không hiểu biết đầy đủ về chủ đề đang được thảo luận.

Rất tiếc, tôi hoàn toàn quên cung cấp giải pháp .. Đây là mã tôi sử dụng trên IPAD không có hướng dẫn cấp lắp ráp cho tác vụ:

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

Điều cần hiểu ở đây là không phải so sánh là đắt, mà là nhánh xảy ra sau so sánh. So sánh trong trường hợp này buộc phải có giá trị 0 hoặc 1 với .. == 0, và kết quả được sử dụng để kết hợp phép toán đã xảy ra ở hai bên của nhánh.

Biên tập:

Đoạn mã trên hoàn toàn bị hỏng. Mã này hoạt động và vẫn không có nhánh (nếu được tối ưu hóa):

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

Điều này trả về -1 nếu cho 0. Nếu bạn không quan tâm đến 0 hoặc hài lòng khi lấy 31 cho 0, hãy loại bỏ phép tính i0, tiết kiệm một khoảng thời gian.


3
Tôi cố định nó cho bạn. Hãy chắc chắn để kiểm tra những gì bạn đăng.
Jim Balter

5
Làm thế nào bạn có thể gọi nó là "không có chi nhánh" khi nó bao gồm một toán tử bậc ba trong đó?
BoltBait

2
Nó là một di chuyển có điều kiện. Một lệnh hợp ngữ duy nhất nhận cả hai giá trị có thể làm tham số và thực hiện thao tác mov dựa trên việc đánh giá điều kiện. Và do đó là "Chi nhánh Miễn phí". không có chuyển đến một địa chỉ không xác định hoặc có thể không chính xác.
Dan

FWIW gcc tạo ra các nhánh ngay cả trên -O3 godbolt.org/z/gcsUHd
Qix - MONICA ĐƯỢC HỖN HỢP

7

Lấy cảm hứng từ bài đăng tương tự này liên quan đến việc tìm kiếm một tập hợp bit, tôi đưa ra những điều sau:

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

Ưu điểm:

  • không có vòng lặp
  • không phân nhánh
  • chạy trong thời gian không đổi
  • xử lý giá trị = 0 bằng cách trả về một kết quả khác nằm ngoài giới hạn
  • chỉ có hai dòng mã

Nhược điểm:

  • giả định độ bền nhỏ như được mã hóa (có thể được sửa bằng cách thay đổi các hằng số)
  • giả sử rằng double là một float * 8 IEEE thực (IEEE 754)

Cập nhật: Như đã chỉ ra trong các nhận xét, liên minh là một triển khai gọn gàng hơn (ít nhất là đối với C) và trông giống như sau:

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

Điều này giả định các int 32-bit với ít bộ nhớ cuối cho mọi thứ (hãy nghĩ đến bộ xử lý x86).


1
Thật thú vị - tôi vẫn sợ sử dụng nhân đôi cho số học bit, nhưng tôi sẽ ghi nhớ điều đó
peterchen

Sử dụng frexp () có thể làm cho nó di động nhiều hơn một chút
aka.nice

1
Đánh kiểu bằng cách ép kiểu con trỏ không an toàn trong C hoặc C ++. Sử dụng memcpy trong C ++ hoặc union trong C. (Hoặc union trong C ++ nếu trình biên dịch của bạn đảm bảo nó an toàn. Ví dụ: các phần mở rộng GNU cho C ++ (được nhiều trình biên dịch hỗ trợ) đảm bảo union type-punning an toàn.)
Peter Dây

1
Gcc cũ hơn cũng tạo mã tốt hơn với liên hợp thay vì truyền con trỏ: nó chuyển trực tiếp từ FP reg (xmm0) sang rax (với movq) thay vì lưu trữ / tải lại. Gcc và clang mới hơn sử dụng movq cho cả hai cách. Xem godbolt.org/g/x7JBiL để biết phiên bản công đoàn. Bạn có chủ ý thực hiện một phép chuyển số học với 20 không? Giả định của bạn cũng nên danh sách đó intint32_t, và rằng sự thay đổi ngay chữ ký là một sự thay đổi số học (trong C ++ nó thực hiện xác định)
Peter Cordes

1
Ngoài ra BTW, Visual Studio (ít nhất là 2013) cũng sử dụng cách tiếp cận test / setcc / sub. Bản thân tôi thích cmp / adc hơn.
DocMax

5

Nó có thể được thực hiện với trường hợp xấu nhất là ít hơn 32 hoạt động:

Nguyên tắc: Kiểm tra 2 hoặc nhiều bit cũng hiệu quả như kiểm tra 1 bit.

Vì vậy, chẳng hạn như không có gì ngăn bạn kiểm tra xem nhóm của nó trong nhóm nào trước, sau đó kiểm tra từng bit từ nhỏ nhất đến lớn nhất trong nhóm đó.

Vì vậy, ...
nếu bạn kiểm tra 2 bit cùng một lúc, bạn có trong trường hợp xấu nhất (Nbits / 2) + 1 lần kiểm tra tổng.
nếu bạn kiểm tra 3 bit cùng một lúc, bạn có trong trường hợp xấu nhất (Nbits / 3) + tổng cộng 2 lần kiểm tra.
...

Tối ưu sẽ là kiểm tra theo nhóm 4. Trong trường hợp xấu nhất sẽ yêu cầu 11 hoạt động thay vì 32 của bạn.

Trường hợp tốt nhất là từ 1 lần kiểm tra trong thuật toán của bạn đến 2 lần kiểm tra nếu bạn sử dụng ý tưởng nhóm này. Nhưng thêm 1 chi phiếu trong trường hợp tốt nhất là giá trị nó để tiết kiệm trong trường hợp xấu nhất.

Lưu ý: Tôi viết nó ra đầy đủ thay vì sử dụng vòng lặp vì nó hiệu quả hơn theo cách đó.

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 từ tôi. Nó không phải là nhanh nhất nhưng nó nhanh hơn so với bản gốc, đó là điểm ...
Andrew Grant

@ onebyone.livejournal.com: Ngay cả khi có lỗi trong mã, khái niệm nhóm là điểm tôi đang cố gắng vượt qua. Mẫu mã thực tế không quan trọng lắm và nó có thể được làm nhỏ gọn hơn nhưng kém hiệu quả hơn.
Brian R. Bondy

Tôi chỉ tự hỏi liệu câu trả lời của tôi có một phần thực sự không tốt hay mọi người không thích điều đó mà tôi đã viết ra đầy đủ?
Brian R. Bondy

@ onebyone.livejournal.com: Khi bạn so sánh 2 thuật toán, bạn nên so sánh chúng như hiện tại, không giả định rằng một thuật toán sẽ được chuyển đổi một cách kỳ diệu bởi một giai đoạn tối ưu hóa. Tôi chưa bao giờ khẳng định thuật toán của mình là "nhanh hơn". Chỉ có điều đó là ít hoạt động hơn.
Brian R. Bondy

@ onebyone.livejournal.com: ... Tôi không cần thiết lập hồ sơ mã trên để biết nó là ít hoạt động hơn. Tôi có thể thấy rõ điều đó. Tôi chưa bao giờ đưa ra bất kỳ yêu cầu nào yêu cầu hồ sơ.
Brian R. Bondy

4

Tại sao không sử dụng tìm kiếm nhị phân ? Điều này sẽ luôn hoàn thành sau 5 thao tác (giả sử kích thước int là 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 Điều này rất giống với câu trả lời của tôi. Thời gian chạy trường hợp tốt nhất là tệ hơn đề xuất của tôi, nhưng thời gian chạy trường hợp xấu nhất thì tốt hơn.
Brian R. Bondy

2

Một phương pháp khác (phân chia mô-đun và tra cứu) đáng được đề cập đặc biệt ở đây từ cùng một liên kết được cung cấp bởi @ anton-tykhyy. phương pháp này có hiệu suất rất giống với phương pháp nhân và tra cứu DeBruijn với một sự khác biệt nhỏ nhưng quan trọng.

phân chia mô-đun và tra cứu

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

phương pháp phân chia và tra cứu mô-đun trả về các giá trị khác nhau cho v = 0x00000000 và v = FFFFFFFF trong khi phương thức nhân và tra cứu DeBruijn trả về số không trên cả hai đầu vào.

kiểm tra:-

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
modlà chậm. Thay vào đó, bạn có thể sử dụng phương pháp nhân và tra cứu ban đầu và trừ !vtừ rđể xử lý các trường hợp cạnh.
Eitan T

3
@EitanT một ưu cũng có thể chuyển đổi mod đó vào một phép nhân nhanh giống như trong các hacker thỏa thích
phuclv

2

Theo trang BitScan lập trình cờ vua và các phép đo của riêng tôi, phép trừ và xor nhanh hơn so với phủ định và mặt nạ.

(Lưu ý hơn là nếu bạn định đếm các số không ở cuối 0, phương thức như tôi có nó trả về 63trong khi hàm phủ định và mặt nạ trả về 0.)

Đây là một phép trừ và xor 64 bit:

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

Để tham khảo, đây là phiên bản 64 bit của phương thức phủ định và mặt nạ:

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

Công (v ^ (v-1))trình này đã cung cấp v != 0. Trong trường hợp v == 0nó trả về 0xFF .... FF trong khi trả về (v & -v)0 (nhân tiện cũng sai, ít nhất nó cũng dẫn đến một kết quả hợp lý).
CiaPan

@CiaPan: Đó là một điểm tốt, tôi sẽ đề cập đến nó. Tôi đoán có một số De Bruijn khác sẽ giải quyết vấn đề này bằng cách đặt 0 vào chỉ số 63.
jnm2

Duh, đó không phải là vấn đề. 0 và 0x8000000000000000 đều dẫn đến 0xFFFFFFFFFFFFFFFF sau đó v ^ (v-1), vì vậy không có sự phân biệt chúng. Trong kịch bản của tôi, số 0 sẽ không bao giờ được nhập.
jnm2

1

Bạn có thể kiểm tra xem có bất kỳ bit nào có thứ tự thấp hơn được đặt hay không. Nếu vậy thì hãy nhìn vào thứ tự thấp hơn của các bit còn lại. ví dụ,:

32bit int - kiểm tra xem có bất kỳ mã nào trong số 16 đầu tiên được đặt hay không. Nếu vậy, hãy kiểm tra xem có bất kỳ cái nào trong 8 cái đầu tiên được đặt hay không. nếu vậy, ....

nếu không, hãy kiểm tra xem có bất kỳ giá trị nào trong số 16 trên đã được đặt chưa ..

Về cơ bản đó là tìm kiếm nhị phân.


1

Xem câu trả lời của tôi tại đây để biết cách thực hiện với một lệnh x86 duy nhất, ngoại trừ việc tìm bit set ít quan trọng nhất mà bạn sẽ muốn hướng dẫn BSF("bit scan forward") thay vì BSRđược mô tả ở đó.


1

Tuy nhiên, một giải pháp khác, không phải là nhanh nhất có thể, nhưng có vẻ khá tốt.
Ít nhất nó không có chi nhánh. ;)

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

để có được tất cả các 1s từ ít nhất đáng kể từ 1 tới LSB, sử dụng ((x & -x) - 1) << 1thay vì
phuclv

một thậm chí nhanh hơn cách: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% của tất cả các số sẽ trả về trên dòng mã đầu tiên.

75% tổng số sẽ trả về trên 2 dòng mã đầu tiên.

87% tất cả các số sẽ trả về trong 3 dòng mã đầu tiên.

94% của tất cả các số sẽ trả về trong 4 dòng mã đầu tiên.

97% tất cả các số sẽ trả về trong 5 dòng mã đầu tiên.

Vân vân.

Tôi nghĩ rằng những người đang phàn nàn về trường hợp xấu nhất đối với mã này không hiệu quả như thế nào không hiểu tình trạng đó sẽ xảy ra hiếm như thế nào.


3
Và một trường hợp tồi tệ nhất của 32 chi nhánh misprediction :)

1
Điều này ít nhất có thể được thực hiện thành một công tắc ...?
Steven Lu

"Điều này ít nhất có thể được thực hiện thành một công tắc ...?" Bạn đã cố gắng làm điều đó trước khi ngụ ý rằng nó có thể? Kể từ khi nào bạn có thể thực hiện các phép tính ngay trên các trường hợp của một công tắc? Đó là một bảng tra cứu, không phải một lớp.
j riv

1

Tìm thấy mẹo thông minh này bằng cách sử dụng 'mặt nạ ma thuật' trong "Nghệ thuật lập trình, phần 4", thực hiện nó trong thời gian O (log (n)) cho số n-bit. [với log (n) không gian thừa]. Các giải pháp điển hình kiểm tra bit thiết lập là O (n) hoặc cần thêm không gian O (n) cho bảng tra cứu, vì vậy đây là một thỏa hiệp tốt.

Mặt nạ ma thuật:

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

Ý tưởng chính: Không có số 0 ở cuối trong 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

Nếu C ++ 11 có sẵn cho bạn, một trình biên dịch đôi khi có thể làm nhiệm vụ cho bạn :)

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

Kết quả là chỉ số dựa trên 1.


1
Thông minh, nhưng nó biên dịch để lắp ráp tồi tệ một cách thảm khốc khi đầu vào không phải là hằng số thời gian biên dịch. godbolt.org/g/7ajMyT . (Một vòng lặp câm trên các bit với gcc hoặc một lệnh gọi hàm đệ quy thực tế với clang.) Gcc / clang có thể đánh giá ffs()tại thời điểm biên dịch, vì vậy bạn không cần sử dụng điều này để truyền hằng số hoạt động. (Bạn phải tránh inline-asm, tất nhiên). Nếu bạn thực sự làm cần cái gì đó hoạt động như một C ++ 11 constexpr, bạn vẫn có thể sử dụng GNU C __builtin_ffs.
Peter Cordes

0

Đây là câu trả lời của @Anton Tykhyy

Đây là triển khai Constexpr C ++ 11 của tôi loại bỏ phôi và loại bỏ cảnh báo trên VC ++ 17 bằng cách cắt ngắn kết quả 64 bit thành 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];
}

Để giải quyết vấn đề 0x1 và 0x0 đều trả về 0, bạn có thể làm:

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

nhưng nếu trình biên dịch không thể hoặc không xử lý trước cuộc gọi, nó sẽ thêm một vài chu kỳ vào phép tính.

Cuối cùng, nếu quan tâm, đây là danh sách các xác nhận tĩnh để kiểm tra xem mã có thực hiện những gì dự định:

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

Đây là một giải pháp thay thế đơn giản, mặc dù việc tìm kiếm nhật ký hơi tốn kém.

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

-3

gần đây mình thấy thủ tướng singapore có đăng chương trình do mình viết trên facebook, có một dòng muốn nhắc đến ..

Logic đơn giản là "giá trị & -value", giả sử bạn có 0x0FF0, sau đó, 0FF0 & (F00F + 1), bằng 0x0010, có nghĩa là giá trị 1 thấp nhất ở bit thứ 4 .. :)


1
Điều này cô lập bit thấp nhất nhưng không cung cấp cho bạn vị trí của nó, đó là những gì câu hỏi này đang yêu cầu.
rhashimoto

Tôi cũng không nghĩ điều này có tác dụng với việc tìm kiếm chút cuối cùng.
yyny

giá trị & ~ giá trị là 0.
kWh

Rất tiếc, mắt tôi đang bị kém. Tôi nhầm một dấu trừ với một dấu ngã. bỏ qua bình luận của tôi
khw

-8

Nếu bạn có đủ tài nguyên, bạn có thể hy sinh bộ nhớ để cải thiện tốc độ:

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

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

Lưu ý: Bảng này sẽ tiêu tốn ít nhất 4 GB (16 GB nếu chúng tôi để loại trả lại làunsigned ). Đây là một ví dụ về giao dịch một tài nguyên giới hạn (RAM) cho một tài nguyên khác (tốc độ thực thi).

Nếu chức năng của bạn cần duy trì khả năng di động và chạy nhanh nhất có thể bằng bất kỳ giá nào, đây sẽ là cách tốt nhất. Trong hầu hết các ứng dụng trong thế giới thực, bảng 4GB là không thực tế.


1
Phạm vi của đầu vào đã được xác định bởi loại paramater - 'unsigned' là giá trị 32 bit, vì vậy không, bạn không ổn.
Brian

3
umm ... hệ thống và hệ điều hành thần thoại của bạn có khái niệm về bộ nhớ được phân trang không? Điều đó sẽ tốn bao nhiêu thời gian?
Mikeage

14
Đây là một câu trả lời không Giải pháp của bạn hoàn toàn không thực tế trong TẤT CẢ các ứng dụng trong thế giới thực và việc gọi nó là "sự đánh đổi" là điều không cần thiết. Hệ thống thần thoại của bạn có 16GB ram để dành cho một chức năng duy nhất không tồn tại. Bạn cũng đã từng trả lời "sử dụng máy tính lượng tử".
Brian

3
Hy sinh bộ nhớ cho tốc độ? Bảng tra cứu 4GB + sẽ không bao giờ phù hợp với bộ nhớ cache trên bất kỳ máy nào hiện có, vì vậy tôi tưởng tượng rằng điều này có thể chậm hơn hầu hết các câu trả lời khác ở đây.

1
Argh. Câu trả lời kinh khủng này cứ ám ảnh tôi :)@Dan: Bạn nói đúng về bộ nhớ đệm. Xem bình luận của Mikeage ở trên.
e.James
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.