【问题标题】:Fastest implementation of log2(int) and log2(float)log2(int) 和 log2(float) 的最快实现
【发布时间】:2013-04-12 09:06:27
【问题描述】:

问题是

还有其他(和/或更快的)基本 2log 实现吗?

应用程序

log2(int) 和 log2(float) 操作在许多不同的上下文中都非常有用。仅举几例:压缩算法、3d 引擎和机器学习。在几乎所有这些上下文中,它们都被用于调用数十亿次的低级代码中……尤其是 log2(int) 操作非常有用。

因为我发现自己一直在使用 log2,所以我不想在这里给出我正在开发的特定应用程序。相同的是,这是一个真正的性能消耗者(如各种应用程序的性能测试所示)。对我来说,尽快完成这项工作是关键。

测试所有实现的完整源代码添加在底部,您可以自己查看。

当然...至少运行 3 次测试,并确保计数器足够大,可以击中几秒钟。我还执行“添加”操作以确保 JIT'ter 不会神奇地删除整个循环。那么让我们开始真正的工作吧。

简单的实现

2log 在 C# 中的简单实现是:

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

这个实现很简单,但也很慢。它需要 2 个日志操作,这本身就已经很慢了。当然,我们可以通过将1.0/Math.Log(2) 设为常量来优化这一点。

请注意,我们需要稍微修改这个常数以获得正确的结果(作为浮点错误的结果)或添加一个小数字以获得正确的结果。我选择了后者,但这并不重要——最终结果在所有情况下都很慢。

查表

一个更快的解决方案是使用查找表。虽然您可以使用查找表 任何 2 的幂,我通常使用 256 或 64K 条目的表大小。

首先我们创建查找表:

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

接下来,我们实现2log如下:

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

如您所见,表查找是一种非常非常快速的实现方式 - 但作为一个缺点,它不能用于计算 log2(float)

删除分支

众所周知,处理器不擅长分支,所以我认为可以通过删除分支来改进表查找。我引入了第二个表,而不是一堆 if,我引入了第二个表,其中包含值和移位位以在表中找到条目:

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

如果你运行这个测试,你会发现它实际上比 if-then-else 解决方案要慢。

然后是英特尔 80386

英特尔多年前就明白这是一项重要的操作,因此他们在其处理器中实施了位扫描转发 (BSF)。其他处理器也有类似的指令。这是迄今为止我所知道的最快的 2log 方法 - 但不幸的是,我现在知道如何使用 C# 中的这些好功能......我不喜欢有一个不再运行的实现的想法当新的平板电脑或手机上市时 - 我不知道有任何跨平台解决方案可以让我直接使用此功能。

其他实现

正如 l4V 指出的那样(谢谢!)还有一些其他的实现,具体来说:

  • 简单的循环。我省略了这一点,因为这很简单,这并不是真的很快。在TestTrivial 中实施。
  • 可以使用的 64 位 IEEE / int union。在TestFloat中实现
  • DeBruijn 查找表。在TestDeBruijn中实现
  • 二进制搜索。在TestBinary中实现

除了我喜欢这个名字之外,DeBruijn 查找表与普通查找表一样快,使其成为这里最快的算法之一……我尝试过的所有其他算法都慢得多。

完整的测试代码

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

【问题讨论】:

  • @I4V,这是一个很好的答案。
  • @l4V 啊,是的,这些来自 Hackers Delight 一书中。我将测试其余的并将它们添加到代码中。让我们看看它们在 C# 中的速度有多快……我不止一次感到惊讶……
  • Stefan de Bruijin 使用 de Bruijin 序列,似乎是正确的 :)
  • @ydroneaud 是的,它速度快,内存效率高,看起来很酷,而且它的名字很漂亮——绝对是我个人迄今为止最喜欢的 :-) 不过,我觉得这应该可以更快.. .

标签: c# .net performance math


【解决方案1】:

采用已经提到的二进制解决方案并删除了分支。做了一些测试,结果证明它比 DeBruijn 快 1.3 倍。

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

【讨论】:

  • 非常有趣;前段时间我在 C++ 中创建了完全相同的东西(可能使用 AVX2 内在函数),结果证明它更慢。有空的时候我会再研究一下,谢谢。
  • 在我的 C# 4.0 基准测试中,此方法的执行速度比 atlaste 答案中的 int LogDeBruijn(int v) 慢 20%(我也发现它是最好的)。
  • @SpecialSauce,你是把算法放在for循环本身还是在for循环中调用的方法?我总是将我要进行基准测试的算法放在 for 循环中,因为 C# 中的方法调用很慢,而且我没有测试方法调用。您是否还确保在发布模式下对其进行了测试?
【解决方案2】:

There are some integer algorithms here.

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

您应该查看我为上下文链接的网站上的原始代码,尤其是 Log2(0) 发生的情况。

【讨论】:

  • 谢谢。我专门尝试了FloorLog2,因为它类似于我正在做的事情。但是,恐怕我发现该算法的速度是 DeBruijn 和单一查找解决方案的两倍......
  • 有趣 - 嗯,值得一试! :)
  • 好的,谢谢。我还惊讶地发现,在 C# 中,表查找通常比其他方法更快......不过,我有点难过,自 1985 年以来几乎所有 CPU 中都有一条指令可以在不计算任何东西的情况下完成所有工作...... . 当您使用“复杂的语言”时,它似乎就遥不可及...
  • @StefandeBruijn 使用 P/Invoke 怎么样?
  • @SargeBorsch 在我的测试中比这些替代方案慢。
【解决方案3】:

更多算法请看这里http://www.asmcommunity.net/forums/topic/?id=15010

还用 C++ 做了一些测试,我的 BSR 实现比查找表慢

  • 我正在使用 BDS2006,可能会因 asm 指令的状态推送/弹出而变慢
  • 您的查找很好,但我使用的是 11 位表而不是 8 位
  • 它将 32 位分成 3 个分支而不是 4 个
  • 而且它仍然足够小,可以在没有初始化函数的情况下处理

代码:

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

测试代码:

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

我在 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

注意:

  • log2(0) -> 0 因为使用了 DWORDS,实际上应该是 -inf
  • 所有其他值对于所有函数都是正确的

【讨论】:

  • BSR 在 AMD 上是出了名的慢(在 Intel 上快)
  • 可能是,但是 asm 指令减慢在那里......已经踩了很多次(asm 实现比 C++ 慢,除非 asm 速度增益克服了状态推送/弹出的东西)现在我唯一被 asm 加速的是来自 32 位 uint 操作的 mul 和 div/mod
  • 那个,你能用 declspec(naked) 还是用你链接的普通 asm 试试?
  • 我还要确保测试 DeBruijn 方法;它需要的数据非常少,它可能适合缓存行——这可能会给你带来更好的结果。
【解决方案4】:
inline int fast_log2(register double x)
{ 
    return (reinterpret_cast<uint64_t&>(x) >> 52) - 1023;
};

【讨论】:

    【解决方案5】:

    有很多答案提供了log2(int) 的快速近似方法,但log2(float) 的答案很少,所以这里有两个(给出的Java 实现)同时使用查找表和尾数/位黑客:

    快速准确的 log2(float):

    /**
     * Calculate the logarithm to base 2, handling special cases.
     */
    public static float log2(float x) {
    
        final int bits = Float.floatToRawIntBits(x);
        final int e = (bits >> 23) & 0xff;
        final int m = (bits & 0x7fffff);
    
        if (e == 255) {
            if (m != 0) {
                return Float.NaN;
            }
            return ((bits >> 31) != 0) ? Float.NaN : Float.POSITIVE_INFINITY;
        }
    
        if ((bits >> 31) != 0) {
            return (e == 0 && m == 0) ? Float.NEGATIVE_INFINITY : Float.NaN;
        }
    
        return (e == 0 ? data[m >>> qm1] : e + data[((m | 0x00800000) >>> q)]);
    }
    

    注意:

    • 如果参数为 NaN 或小于零,则结果为 NaN。
    • 如果参数为正无穷大,则结果为正无穷大。
    • 如果参数为正零或负零,则结果为负无穷大。

    快速准确的 log2(float)(稍微快一点,不检查)

    /**
     * Calculate the logarithm using base 2. Requires the argument be finite and
     * positive.
     */
    public static float fastLog2(float x) {
        final int bits = Float.floatToRawIntBits(x);
        final int e = (bits >> 23) & 0xff;
        final int m = (bits & 0x7fffff);
        return (e == 0 ? data[m >>> qm1] : e + data[((m | 0x00800000) >>> q)]);
    }
    

    第二种方法放弃了另一种方法中存在的检查,因此具有以下特殊情况:

    • 如果参数为 NaN,则结果不正确。
    • 如果参数是否定的,则结果不正确。
    • 如果参数为正无穷大,则结果不正确。
    • 如果参数为正零或负零,则结果为负无穷大。

    这两种方法都依赖于查找表data(以及变量qqm1)。这些使用以下方法填充。 n 定义了精度空间权衡。

    static int q, qm1;
    static float[] data;
    
    /**
     * Compute lookup table for a given base table size.
     * 
     * @param n The number of bits to keep from the mantissa. Table storage =
     *          2^(n+1) * 4 bytes, e.g. 64Kb for n=13. Must be in the range
     *          0<=n<=23
     */
    public static void populateLUT(int n) {
    
        final int size = 1 << (n + 1);
    
        q = 23 - n;
        qm1 = q - 1;
        data = new float[size];
    
        for (int i = 0; i < size; i++) {
            data[i] = (float) (Math.log(i << q) / Math.log(2)) - 150;
        }
    }
    

    populateLUT(12);
    log2(6666); // = 12.702606
    

    【讨论】:

      【解决方案6】:

      另一个 log2(int) 函数:(不再是最快的)

          [StructLayout(LayoutKind.Explicit)]
          private struct ConverterStruct
          {
              [FieldOffset(0)] public int asInt;
              [FieldOffset(0)] public float asFloat;
          }
      
          public static int Log2(uint val)
          {
              ConverterStruct a;  a.asInt = 0; a.asFloat = val;
              return ((a.asInt >> 23 )+ 1) & 0x1F;
          }
      

      注意事项: 在浮点数中使用指数的灵感来自SPWorley 3/22/2009。谨慎使用生产代码,因为这会在非小端架构上失败。

      如果您想要安全的“字节序”,请查看spender 5/3/2012。它也有零支持。

      最快的是新的内置BitOperations.Log2(x)

      以下是一些基准测试:(此处代码:https://github.com/SunsetQuest/Fast-Integer-Log2

      Function               Time1  Full-32-Bit  Zero?   FUNCTION                  
      BitOperationsLog2        2        Yes      Yes     BitOperations.Log2(x);
      LeadingZeroCount         2        Yes      Yes     31 - BitOperations.LeadingZeroCount(x);
      Log2_SunsetQuest5        16       Yes      No      ((BitConverter.DoubleToInt64Bits(val)>>52)+1) & 0xFF;
      Log2_WiegleyJ            17       Yes      Yes     ...
      MostSigBit_spender       17       Yes      Yes     ...
      Log2_SPWorley            17       Yes      Yes     ...
      Log2_SunsetQuest4        18       Yes      No      ...
      FloorLg2_Matthew_Watson  18       Yes      Yes     ...
      Log2_SunsetQuest3        19       Yes      No      ...
      Log2_SunsetQuest1        20       Yes      Yes     ...
      Log2_HarrySvensson       20       Yes      Yes     ...
      Log2_DanielSig           21       No       Yes     ...
      HighestBitUnrolled_Kaz   25       Yes      Yes     ...
      FloorLog2_SN17           36       Yes      Yes     ...
      Log2_Papayaved           44       Yes      Yes     ...
      GetMsb_user3177100       45       Yes      Yes     ...
      Log2_Flynn1179           57       Yes      Yes     ...
      Msb_Protagonist          63       Yes      Yes     ...
      SomeOtherMethod          76       Yes      Yes     ...
      Log2_SunsetQuest0        98       Yes      Yes     ...
      Log2_SunsetQuest2        131      Yes      Yes     ...
      SomeOtherMethod          202      Yes      Yes     ...
      SomeOtherMethod          545      Yes      Yes     ...
      
      Zero_Support    = Supports Neg Return on Zero
      Full-32-Bit     = Supports full 32-bit (some just support 31 bits)
      SomeOtherMethod = name of function/person left out on purpose
      Benchmark notes: AMD Ryzen, Release, no-debugger attached, .net 6.0
      

      【讨论】:

        【解决方案7】:

        清洁可靠且快速!
        (需要 .net core 3 或更高版本)

        int val = BitOperations.Log2(x);
        

        【讨论】:

          【解决方案8】:
              static byte FloorLog2(UInt16 value)
              {
                  for (byte i = 0; i < 15; ++i)
                  {
                      if ((value >>= 1) < 1)
                      {
                          return i;
                      }
                  }
                  return 15;
              }
          

          【讨论】:

          • 最好至少添加一条关于代码示例如何解决原始问题中的问题的小注释。
          【解决方案9】:

          (我没有做过任何测量,所以这可能不匹配,但我认为用户 user9337139 的想法很巧妙,想在 C# 中尝试同样的方法 - 他是 C++)。

          这是一个 C# int Magnitude(byte) 函数,它基于将字节值转换为浮点数并从 IEEE float representation 中提取指数。

              using System.Runtime.InteropServices;
          
              [StructLayout(LayoutKind.Explicit)]
              struct UnionWorker
              {
                  [FieldOffset(0)]
                  public int i;
                  [FieldOffset(0)]
                  public float f;
              }
          
              static int Magnitude(byte b)
              {
                  UnionWorker u;
                  u.i = 0; // just to please the compiler
                  u.f = b;
                  return Math.Max((u.i >> 23) & 0xFF, 126) - 126;
              }
          

          返回 0 表示 0,8 表示 0xFF,其他值如您所愿。

          零是一个特殊情况,所以我需要Math.Max 钳位。我怀疑 user9337139 的解决方案可能有类似的问题。

          注意,这没有测试过字节顺序问题 - 警告购买者。

          【讨论】:

          • 我刚刚注意到您的回答,并认为这是我的副本,但后来我查看了日期。我在您之后几天发布了相同的答案(请参阅 GitHub 。在同一日期如此接近的答案的几率是多少?无论如何,我只是想我会分享这种世俗的一致。
          猜你喜欢
          • 1970-01-01
          • 2021-08-06
          • 2018-01-27
          • 2012-07-07
          • 1970-01-01
          • 2019-08-18
          • 2013-07-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多