2013-04-12 13 views
20

Câu hỏi đặt ra làthực hiện nhanh nhất của log2 (int) và log2 (float)

Có bất kỳ khác (và/hoặc nhanh hơn) triển khai của một 2log cơ bản?

Applications

Các log2 (int) và (float) hoạt động log2 là rất hữu ích trong rất nhiều hoàn cảnh khác nhau. Để đặt tên một vài: thuật toán nén, động cơ 3d và học máy. Trong hầu hết các bối cảnh này, chúng được sử dụng trong mã mức thấp được gọi là hàng tỉ lần ... Đặc biệt hoạt động log2 (int) rất hữu ích.

Bởi vì tôi thấy mình sử dụng log2 mọi lúc, tôi không muốn cung cấp cho một ứng dụng cụ thể mà tôi đang làm việc ở đây. Điều tương tự là thực tế rằng đây là một drainer hiệu suất thực (như được hiển thị bởi các bài kiểm tra hiệu suất của các ứng dụng khác nhau). Đối với tôi đó là chìa khóa để có được điều này càng nhanh càng tốt.

Mã nguồn hoàn chỉnh để kiểm tra tất cả các triển khai được thêm ở dưới cùng, vì vậy bạn có thể tự mình xem.

Và tất nhiên ... chạy thử nghiệm của bạn ít nhất 3 lần và đảm bảo rằng các quầy là đủ lớn để đạt nhiều giây. Ngoài ra tôi thực hiện thao tác 'thêm' để đảm bảo toàn bộ vòng lặp không được JIT'ter loại bỏ một cách kỳ diệu. Vì vậy, chúng ta hãy bắt đầu với công việc thực sự.

thực hiện Trivial

Việc thực hiện tầm thường của một 2log trong C# là:

(int)(Math.Log(x)/Math.Log(2)) 

thực hiện này là tầm thường, nhưng cũng rất chậm. Nó đòi hỏi 2 hoạt động Log, đó là bản thân nó khá chậm. Tất nhiên, chúng ta có thể tối ưu hóa điều này bằng cách làm cho 1.0/Math.Log(2) một hằng số.

Lưu ý rằng chúng ta cần sửa đổi hằng số này một chút để có được kết quả đúng (do lỗi dấu phẩy động) hoặc thêm một số nhỏ để có kết quả chính xác. Tôi đã chọn thứ hai, nhưng nó không thực sự quan trọng - kết quả cuối cùng là chậm trong mọi trường hợp.

Bảng tra cứu

Một giải pháp nhanh hơn cho điều này là sử dụng một bảng tra cứu. Trong khi bạn có thể sử dụng bảng tra cứu của bất kỳ sức mạnh nào của 2, tôi thường sử dụng kích thước bảng là 256 hoặc 64K mục.

Đầu tiên chúng ta tạo ra các bảng tra cứu:

lookup = new int[256]; 
for (int i = 1; i < 256; ++i) 
{ 
    lookup[i] = (int)(Math.Log(i)/Math.Log(2)); 
} 

Tiếp theo, chúng ta thực hiện các 2log như sau:

private static int LogLookup(int i) 
{ 
    if (i >= 0x1000000) { return lookup[i >> 24] + 24; } 
    else if (i >= 0x10000) { return lookup[i >> 16] + 16; } 
    else if (i >= 0x100) { return lookup[i >> 8] + 8; } 
    else { return lookup[i]; } 
} 

Như bạn thấy, tra cứu bảng là một nhiều, thực hiện nhanh hơn nhiều - nhưng như một con nó không thể được sử dụng để tính toán log2(float).

loại bỏ chi nhánh

Như chúng ta đều biết, bộ vi xử lý không phải là rất tốt ở phân nhánh, vì vậy tôi figured rằng tra cứu bảng có thể được cải thiện bằng cách loại bỏ các chi nhánh.Thay vì các chùm nếu của tôi giới thiệu một bảng thứ hai với các giá trị và dịch chuyển bit xung quanh để tìm mục trong bảng:

nobranch = new int[16] { 0, 0, 8, 8, 16, 16, 16, 16, 24, 24, 24, 24, 24, 24, 24, 24 }; 

private static int LogDoubleLookup(int i) 
{ 
    int n = (i | (i >> 4)); 
    n = (n | (n >> 2)); 
    n = (n | (n >> 1)); 
    n = ((n & 0x1000000) >> 21) | ((n & 0x10000) >> 14) | ((n & 0x100) >> 7) | (n & 1); 
    int br = nobranch[n]; 
    return lookup[i >> br] + br; 
} 

Nếu bạn chạy thử nghiệm này, bạn sẽ thấy rằng nó thực sự là chậm hơn so với nếu - giải pháp khác.

Và sau đó là Intel 80386

Intel hiểu năm trước rằng đây là một hoạt động quan trọng, vì vậy họ thực hiện Bit-Scan-Forward (BSF) vào bộ vi xử lý của họ. Các bộ vi xử lý khác có hướng dẫn tương tự. Đây là cách nhanh nhất để làm một 2log mà tôi biết - nhưng tiếc là tôi biết bây giờ cách để sử dụng các chức năng tốt đẹp từ C# ... Tôi không thích ý tưởng có một thực hiện mà không chạy nữa khi một máy tính bảng hoặc điện thoại mới tung ra thị trường - và tôi không biết bất kỳ giải pháp đa nền tảng nào cho phép tôi sử dụng chức năng này trực tiếp.

triển khai khác

Như l4V chỉ ra (nhờ!) Có một vài hiện thực khác, cụ thể:

  • Trivial vòng lặp. Tôi bỏ qua điều này vì nó tầm thường không thực sự nhanh. Được triển khai trong TestTrivial.
  • Công nghệ IEEE/int của 64 bit có thể được sử dụng. Triển khai trong TestFloat
  • Bảng tra cứu DeBruijn. Được triển khai trong TestDeBruijn
  • Tìm kiếm nhị phân. Được triển khai trong TestBinary

Ngoài ra tôi thích tên, bảng tra cứu DeBruijn nhanh như bảng tra cứu thông thường, làm cho nó trở thành một trong những thuật toán nhanh nhất ở đây ... tất cả các thuật toán khác mà tôi đã thử chậm hơn nhiều.

Toàn bộ mã kiểm tra

public class Log2Test 
{ 
    public static void TestNaive() 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += (int)(Math.Log(i)/Math.Log(2.0)); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - naive implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    public static int LogTrivialLoop(int v) 
    { 
     int r = 0; 
     while ((v >>= 1) > 0) // unroll for more speed... 
     { 
      r++; 
     } 
     return r; 
    } 

    public static void TestTrivialLoop() 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogTrivialLoop(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - loop implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    public static int LogFloat(int v) 
    { 
     Helper h = new Helper() { U1 = v, U2 = 0x43300000 }; 
     h.D -= 4503599627370496.0; 
     return (h.U2 >> 20) - 0x3FF; 
    } 

    public static void TestFloat() 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogFloat(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - IEEE float implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    [StructLayout(LayoutKind.Explicit)] 
    private struct Helper 
    { 
     [FieldOffset(0)] 
     public int U1; 
     [FieldOffset(4)] 
     public int U2; 
     [FieldOffset(0)] 
     public double D; 
    } 

    public static void TestConstant() 
    { 
     double c = 1.0/Math.Log(2.0); 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += (int)(0.00000000001 + Math.Log(i) * c); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - naive 2 implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    private static int LogLookup(int i) 
    { 
     if (i >= 0x1000000) { return lookup[i >> 24] + 24; } 
     else if (i >= 0x10000) { return lookup[i >> 16] + 16; } 
     else if (i >= 0x100) { return lookup[i >> 8] + 8; } 
     else { return lookup[i]; } 
    } 

    public static void TestLookup() 
    { 
     lookup = new int[256]; 
     for (int i = 1; i < 256; ++i) 
     { 
      lookup[i] = (int)(Math.Log(i)/Math.Log(2)); 
     } 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogLookup(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - table lookup implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    private static int LogDoubleLookup(int i) 
    { 
     int n = (i | (i >> 4)); 
     n = (n | (n >> 2)); 
     n = (n | (n >> 1)); 
     n = ((n & 0x1000000) >> 21) | ((n & 0x10000) >> 14) | ((n & 0x100) >> 7) | (n & 1); 
     int br = nobranch[n]; 
     return lookup[i >> br] + br; 
    } 

    public static void TestDoubleLookup() 
    { 
     // Lookup table was already constructed earlier 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogDoubleLookup(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - double table lookup implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    private static int LogBinary(int v) 
    { 
     /* This is the worst implementation ever... - apparently C# is a slow-branching language 

     int[] b = { 0x2, 0xC, 0xF0, 0xFF00, 0x7FFF0000 }; 
     int[] S = { 1, 2, 4, 8, 16 }; 

     int r = 0; // result of log2(v) will go here 
     for (int i = 4; i >= 0; i--) // unroll for speed... 
     { 
      if ((v & b[i]) != 0) 
      { 
       v >>= S[i]; 
       r |= S[i]; 
      } 
     } 
     return r; 

     */ 

     int r = (((v > 0xFFFF)) ? 0x10 : 0); 
     v >>= r; 
     int shift = ((v > 0xFF) ? 0x8 : 0); 
     v >>= shift; 
     r |= shift; 
     shift = ((v > 0xF) ? 0x4 : 0); 
     v >>= shift; 
     r |= shift; 
     shift = ((v > 0x3) ? 0x2 : 0); 
     v >>= shift; 
     r |= shift; 
     r |= (v >> 1); 
     return r; 
    } 

    public static void TestBinary() 
    { 
     // Lookup table was already constructed earlier 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogBinary(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - binary search implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    private static readonly int[] MultiplyDeBruijnBitPosition = new int[32] 
    { 
     0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 
     8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 
    }; 

    private static int LogDeBruijn(int v) 
    { 
     v |= v >> 1; // first round down to one less than a power of 2 
     v |= v >> 2; 
     v |= v >> 4; 
     v |= v >> 8; 
     v |= v >> 16; 

     return MultiplyDeBruijnBitPosition[(uint)(v * 0x07C4ACDDU) >> 27]; 
    } 

    public static void TestDeBruijn() 
    { 
     // Lookup table was already constructed earlier 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     int n = 0; 
     for (int i = 1; i < 100000000; ++i) 
     { 
      n += LogDeBruijn(i); 
     } 
     sw.Stop(); 
     Console.WriteLine("Result: {0} - de Bruijn implementation took {1:0.000}s", n, sw.Elapsed.TotalSeconds); 
    } 

    private static int[] lookup; 
    private static readonly int[] nobranch = new int[16] { 0, 0, 8, 8, 16, 16, 16, 16, 24, 24, 24, 24, 24, 24, 24, 24 }; 

    static void Main(string[] args) 
    { 
     TestConstant(); 
     TestNaive(); 
     TestDeBruijn(); 
     TestBinary(); 
     TestFloat(); 
     TestTrivialLoop(); 
     TestLookup(); 
     TestDoubleLookup(); 
     Console.ReadLine(); 
    } 
} 
+1

http://graphics.stanford.edu/~seander /bithacks.html#IntegerLogObvious – I4V

+0

@ I4V, đó là một câu trả lời tuyệt vời. – Ben

+0

@ l4V ah vâng, đây là những cái từ cuốn sách Hacker Delight. Tôi sẽ kiểm tra những cái còn lại và thêm chúng vào mã. Hãy xem nhanh như thế nào trong C# ... Tôi đã ngạc nhiên nhiều lần ... – atlaste

Trả lời

3

There are some integer algorithms here.

Trong C#:

public static uint FloorLog2(uint x) 
{ 
    x |= (x >> 1); 
    x |= (x >> 2); 
    x |= (x >> 4); 
    x |= (x >> 8); 
    x |= (x >> 16); 

    return (uint)(NumBitsSet(x) - 1); 
} 

public static uint CeilingLog2(uint x) 
{ 
    int y = (int)(x & (x - 1)); 

    y |= -y; 
    y >>= (WORDBITS - 1); 
    x |= (x >> 1); 
    x |= (x >> 2); 
    x |= (x >> 4); 
    x |= (x >> 8); 
    x |= (x >> 16); 

    return (uint)(NumBitsSet(x) - 1 - y); 
} 

public static int NumBitsSet(uint x) 
{ 
    x -= ((x >> 1) & 0x55555555); 
    x = (((x >> 2) & 0x33333333) + (x & 0x33333333)); 
    x = (((x >> 4) + x) & 0x0f0f0f0f); 
    x += (x >> 8); 
    x += (x >> 16); 

    return (int)(x & 0x0000003f); 
} 

private const int WORDBITS = 32; 

Bạn nên nhìn vào mã gốc trên trang web của tôi liên kết với bối cảnh, đặc biệt là những gì xảy ra với log2 (0).

+0

Cảm ơn. Tôi đã thử đặc biệt 'FloorLog2' vì nó giống với những gì tôi đang làm. Tuy nhiên, tôi e rằng tôi đã tìm được thuật toán chậm gấp đôi so với DeBruijn và các giải pháp tra cứu đơn lẻ ... – atlaste

+0

Thú vị - vâng, nó đáng để bắn! :) –

+1

Chắc chắn, cảm ơn. Tôi cũng ngạc nhiên khi thấy rằng trong C# tra cứu bảng thường nhanh hơn so với các lựa chọn thay thế ... Tuy nhiên, tôi là một chút buồn rằng có một chỉ dẫn trong hầu như tất cả CPU từ năm 1985 mà làm tất cả các công việc mà không tính toán một điều .. nó chỉ có vẻ là ngoài tầm với của bạn vào thời điểm bạn đi đến một 'ngôn ngữ tinh vi' ... – atlaste

2

Đối với thuật toán hơn nhìn đây http://www.asmcommunity.net/forums/topic/?id=15010

Cũng đã làm một số thử nghiệm trong C++ và thực hiện của tôi về BSR là chậm hơn so với bảng tra cứu

  • tôi đang sử dụng BDS2006 có lẽ là làm chậm bởi push bang/popping theo chỉ thị asm
  • tra cứu của bạn tốt nhưng tôi đang sử dụng bảng 11 bit thay vì 8
  • nó chia 32 bit thành 3 chi nhánh thay vì 4
  • và nó vẫn đủ nhỏ để xử lý mà không hàm init

mã:

//--------------------------------------------------------------------------- 
DWORD log2_slow(const DWORD &x) 
    { 
    DWORD m,i; 
    if (!x) return 0; 
    if (x>=0x80000000) return 31; 
    for (m=1,i=0;m<x;m<<=1,i++); 
    if (m!=x) i--; 
    return i; 
    } 
//--------------------------------------------------------------------------- 
DWORD log2_asm(const DWORD &x) 
    { 
    DWORD xx=x; 
    asm { 
     mov eax,xx 
     bsr eax,eax; 
     mov xx,eax; 
     } 
    return xx; 
    } 
//--------------------------------------------------------------------------- 
BYTE _log2[2048]= 
    { 
    0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 
    }; 
DWORD log2(const DWORD &x) 
    { 
     if (x>=0x00400000) return _log2[x>>22]+22; 
    else if (x>=0x00000800) return _log2[x>>11]+11; 
    else     return _log2[x]; 
    } 
//--------------------------------------------------------------------------- 

mã kiểm tra:

DWORD x,j,i,n=256; 
tbeg(); for (i=0;i<32;i++) for (j=0;j<n;j++) x=log2  (j<<i); tend(); mm_log->Lines->Add(tstr(1)); 
tbeg(); for (i=0;i<32;i++) for (j=0;j<n;j++) x=log2_asm (j<<i); tend(); mm_log->Lines->Add(tstr(1)); 
tbeg(); for (i=0;i<32;i++) for (j=0;j<n;j++) x=log2_slow(j<<i); tend(); mm_log->Lines->Add(tstr(1)); 

kết quả của tôi trên bộ xử lý AMD A8-5500 3.2 GHz:

[ 0.040 ms] log2  (x) - 11bit lookup table 
[ 0.060 ms] log2_asm (x) - BSR 
[ 0.415 ms] log2_slow(x) - shift loop 

Lưu ý:

  • log2 (0) -> 0 vì sử dụng dwords, trong thực tế nó phải được -INF
  • tất cả các giá trị khác là chính xác cho tất cả các chức năng
+0

BSR nổi tiếng là chậm trên AMD (nhanh trên Intel) – harold

+0

có thể được nhưng chỉ thị asm chậm lại là có ... đã bước vào nó nhiều lần (asm thực hiện là chậm hơn so với C + + trừ khi đạt được tốc độ asm vượt qua nhà nước đẩy/popping điều này) bây giờ điều duy nhất tôi đã tăng tốc bởi asm là mul và div/mod từ hoạt động uint 32bit – Spektre

+0

Điều đó nữa, bạn có thể thử nó với declspec (thường) hoặc với asm đơn giản mà bạn liên kết trong? – harold

3

Đã giải pháp nhị phân đã được đề cập và loại bỏ nhánh. Đã làm một số thử nghiệm và nó bật ra là 1,3 lần nhanh hơn so với DeBruijn.

public static int Log2(int v) 
{ 
    int r = 0xFFFF - v >> 31 & 0x10; 
    v >>= r; 
    int shift = 0xFF - v >> 31 & 0x8; 
    v >>= shift; 
    r |= shift; 
    shift = 0xF - v >> 31 & 0x4; 
    v >>= shift; 
    r |= shift; 
    shift = 0x3 - v >> 31 & 0x2; 
    v >>= shift; 
    r |= shift; 
    r |= (v >> 1); 
    return r; 
} 
+0

Rất thú vị; Tôi đã tạo ra chính xác điều tương tự trong C++ một số thời gian trước đây (có lẽ với nội tại AVX2), và nó hóa ra là chậm hơn. Tôi sẽ xem xét điều này nhiều hơn một chút khi tôi có thời gian rảnh rỗi, cảm ơn. – atlaste

+0

Trong tiêu chuẩn C# 4.0 của tôi, phương pháp này thực hiện chậm hơn 20% so với 'int LogDeBruijn (int v)' trong câu trả lời của atlaste (mà tôi cũng thấy là tốt nhất). –

+0

@SpecialSauce, bạn đã đặt thuật toán bên trong chính vòng lặp hay phương thức được gọi trong vòng lặp for? Tôi luôn luôn đặt các thuật toán tôi điểm chuẩn bên trong for-loop vì các cuộc gọi phương thức chậm trong C# và tôi không kiểm tra cuộc gọi phương thức. Ngoài ra, bạn có chắc chắn rằng bạn đã thử nghiệm nó trong chế độ phát hành? – DanielSig

0

Dưới đây là những gì tôi đã được sử dụng:

unsigned log2(register unsigned n) { 
    register unsigned i; 
    for (i=0; (n & 1); n>>=1, i++); 
    return i; 
} 

Edit: Kiểm tra này ra (biến thể ffz): https://patchwork.kernel.org/patch/8845631/

+0

Trong khi khái niệm đơn giản, phương thức này thực hiện khá kém đối với các giá trị 'long' với phân phối chung hoặc không xác định. Trong C# 4.0, nó mất ** gấp 4,5 lần ** so với một số cách tiếp cận khác. Thời gian duy nhất phương pháp này có thể hữu ích là nếu phân phối các con số đã được biết là tất cả gần bằng không vì sau đó phương pháp này thoát sớm sau khi kiểm tra chỉ tương đối ít bit. Tôi nghi ngờ từ khóa 'register' trong C++ là đủ để bù đắp cho những thiếu sót của thuật toán này. –

+0

Chắc chắn nó chậm hơn so với một bảng tra cứu hoặc những phép thuật ma thuật các trình thuật sĩ khác sản xuất nhưng không chậm hơn so với sử dụng số mũ thực tế và log2 tôi chắc chắn. Haha. Gấp 4,5 lần nếu con số cách xa 0. Làm thế nào mà tương quan? Bạn nói nó mất 4,5 lần lâu hơn, trong những tình huống, sau đó? Ngoài ra, hãy kiểm tra [những] (https://patchwork.kernel.org/patch/8845631/). Tôi không biết nếu bạn có chúng trong C#, mặc dù. – quirinpa

1
inline int fast_log2(register double x) 
{ 
    return (reinterpret_cast<uint64_t&>(x) >> 52) - 1023; 
};