使用按位运算[n的基数2中的对数]求n = 2 ** x的指数

时间:2010-02-12 21:16:56

标签: python bit-manipulation primes bitwise-operators logarithm

是否有一种直接的方法可以使用按位运算从2的幂中提取指数?

编辑:虽然这个问题最初是关于按位操作的,但如果您想知道“在Y = 2 X in Python ?“**

我目前正在尝试优化一个例程(Rabin-Miller primality test),以减少表单 2**s * d 中的偶数 N.我可以通过以下方式获得 2**s 部分:

two_power_s = N & -N

但我无法通过按位操作找到提取“ s ”的方法。我目前正在测试的解决方法没有太多的满足感(它们都非常慢):

  • 使用对数函数
  • 操纵2 ** s的二进制表示(即计算尾随零)
  • 在除法上循环2,直到结果为1

我正在使用python,但我认为这个问题的答案应该是语言无关的。

7 个答案:

答案 0 :(得分:6)

“语言不可知”和担心性能几乎是不相容的概念。

大多数现代处理器都有CLZ指令,“计数前导零”。在GCC中,您可以使用__builtin_clz(x)(对于缺少clz的目标也生成合理的,如果不是最快的代码)。请注意,此CLZ未定义为零,因此如果在您的应用程序中重要,则需要额外的分支来捕获该情况。

在CELT(http://celt-codec.org)中,我们用于缺乏CLZ的编译器的无分支CLZ由Timothy B. Terriberry撰写:


int ilog(uint32 _v){
  int ret;
  int m;
  ret=!!_v;
  m=!!(_v&0xFFFF0000)<<4;
  _v>>=m;
  ret|=m;
  m=!!(_v&0xFF00)<<3;
  _v>>=m;
  ret|=m;
  m=!!(_v&0xF0)<<2;
  _v>>=m;
  ret|=m;
  m=!!(_v&0xC)<<1;
  _v>>=m;
  ret|=m;
  ret+=!!(_v&0x2);
  return ret;
}

(评论表明发现这比分支版本和基于查找表的版本更快)

但是如果性能非常重要,那么你可能不应该在python中实现这部分代码。

答案 1 :(得分:5)

简短回答

就python而言:

  • 最快的方法找到2 ** x的指数是通过查找哈希值为2的幂的字典(参见“ hashlookup ”在代码中)
  • 最快按位方法是名为“ unrolled_bitwise ”的方法。
  • 以前的两种方法都有明确定义(但可扩展)的上限。没有硬编码上限最快的方法(扩展到python可以处理数字)是“ log_e ”。

初步说明

  1. 以下所有速度测量都是通过 timeit.Timer.repeat(testn, cycles) 获得的,其中testn设置为3,并且脚本会自动调整cycles以获取范围秒(注意:此自动调整机制中存在一个错误,已于18/02/2010修复)。
  2. 并非所有方法都可以扩展,这就是为什么我没有为2的各种权力测试所有函数
  3. 我没有设法使用某些提议的方法(该函数返回错误的结果)。我还没有做一个循序渐进的调试会话:我包含了代码(注释),以防有人通过检查发现错误(或者想要自己执行调试)
  4. 结果

    <强> FUNC(2 5)**

    hashlookup:          0.13s     100%
    lookup:              0.15s     109%
    stringcount:         0.29s     220%
    unrolled_bitwise:    0.36s     272%
    log_e:               0.60s     450%
    bitcounter:          0.64s     479%
    log_2:               0.69s     515%
    ilog:                0.81s     609%
    bitwise:             1.10s     821%
    olgn:                1.42s    1065%
    

    <强> FUNC(2 31)**

    hashlookup:          0.11s     100%
    unrolled_bitwise:    0.26s     229%
    log_e:               0.30s     268%
    stringcount:         0.30s     270%
    log_2:               0.34s     301%
    ilog:                0.41s     363%
    bitwise:             0.87s     778%
    olgn:                1.02s     912%
    bitcounter:          1.42s    1264%
    

    <强> FUNC(2 128)**

    hashlookup:     0.01s     100%
    stringcount:    0.03s     264%
    log_e:          0.04s     315%
    log_2:          0.04s     383%
    olgn:           0.18s    1585%
    bitcounter:     1.41s   12393%
    

    <强> FUNC(2 1024)**

    log_e:          0.00s     100%
    log_2:          0.01s     118%
    stringcount:    0.02s     354%
    olgn:           0.03s     707%
    bitcounter:     1.73s   37695%
    

    代码

    import math, sys
    
    def stringcount(v):
        """mac"""    
        return len(bin(v)) - 3
    
    def log_2(v):
        """mac"""    
        return int(round(math.log(v, 2), 0)) # 2**101 generates 100.999999999
    
    def log_e(v):
        """bp on mac"""    
        return int(round(math.log(v)/0.69314718055994529, 0))  # 0.69 == log(2)
    
    def bitcounter(v):
        """John Y on mac"""
        r = 0
        while v > 1 :
            v >>= 1
            r += 1
        return r
    
    def olgn(n) :
        """outis"""
        if n < 1:
            return -1
        low = 0
        high = sys.getsizeof(n)*8 # not the best upper-bound guesstimate, but...
        while True:
            mid = (low+high)//2
            i = n >> mid
            if i == 1:
                return mid
            if i == 0:
                high = mid-1
            else:
                low = mid+1
    
    def hashlookup(v):
        """mac on brone -- limit: v < 2**131"""
    #    def prepareTable(max_log2=130) :
    #        hash_table = {}
    #        for p in range(1, max_log2) :
    #            hash_table[2**p] = p
    #        return hash_table
    
        global hash_table
        return hash_table[v] 
    
    def lookup(v):
        """brone -- limit: v < 2**11"""
    #    def prepareTable(max_log2=10) :
    #        log2s_table=[0]*((1<<max_log2)+1)
    #        for i in range(max_log2+1):
    #            log2s_table[1<<i]=i
    #        return tuple(log2s_table)
    
        global log2s_table
        return log2s_table[v]
    
    def bitwise(v):
        """Mark Byers -- limit: v < 2**32"""
        b = (0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000)
        S = (1, 2, 4, 8, 16)
        r = 0
        for i in range(4, -1, -1) :
            if (v & b[i]) :
                v >>= S[i];
                r |= S[i];
        return r
    
    def unrolled_bitwise(v):
        """x4u on Mark Byers -- limit:   v < 2**33"""
        r = 0;
        if v > 0xffff : 
            v >>= 16
            r = 16;
        if v > 0x00ff :
            v >>=  8
            r += 8;
        if v > 0x000f :
            v >>=  4
            r += 4;
        if v > 0x0003 : 
            v >>=  2
            r += 2;
        return r + (v >> 1)
    
    def ilog(v):
        """Gregory Maxwell - (Original code: B. Terriberry) -- limit: v < 2**32"""
        ret = 1
        m = (not not v & 0xFFFF0000) << 4;
        v >>= m;
        ret |= m;
        m = (not not v & 0xFF00) << 3;
        v >>= m;
        ret |= m;
        m = (not not v & 0xF0) << 2;
        v >>= m;
        ret |= m;
        m = (not not v & 0xC) << 1;
        v >>= m;
        ret |= m;
        ret += (not not v & 0x2);
        return ret - 1;
    
    
    # following table is equal to "return hashlookup.prepareTable()" 
    hash_table = {...} # numbers have been cut out to avoid cluttering the post
    
    # following table is equal to "return lookup.prepareTable()" - cached for speed
    log2s_table = (...) # numbers have been cut out to avoid cluttering the post
    

答案 2 :(得分:4)

有一个页面包含很多这些类型的技巧和黑客。它是为C编写的,但其中许多也应该在Python中工作(尽管性能显然会有所不同)。你想要的是here及以后。

您可以尝试this例如:

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

看起来它可以很容易地转换为Python。

答案 3 :(得分:3)

您可以使用binsearch在O(lg s)时间内为任意长度的整数执行此操作。

import sys
def floorlg(n):
    if n < 1:
        return -1
    low=0
    high=sys.getsizeof(n)*8 # not the best upper-bound guesstimate, but...
    while True:
        mid = (low+high)//2
        i = n >> mid
        if i == 1:
            return mid
        if i == 0:
            high = mid-1
        else:
            low = mid+1

对于固定大小的整数,查找表应该是最快的解决方案,并且可能是最好的整体。

答案 4 :(得分:1)

看起来范围已知。我们假设它上升到1 <&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt

max_log2=20

所以制作一个列表(实际上)将一个整数映射到它的基数2对数。以下将解决这个问题:

log2s_table=[0]*((1<<max_log2)+1)
for i in range(max_log2+1):
    log2s_table[1<<i]=i

(这对于不是2的幂的数字没有任何用处;问题陈述表明它们不需要处理。虽然很容易解决这个问题。)

获得对数的函数非常简单,可以很容易地内联:

def table(v):
    return log2s_table[v]

我不能保证我写的测试代码与用于获取示例时序的测试代码完全相同,但这比stringcount代码更快:

stringcount: 0.43 s.
table: 0.16 s.

由于表中的所有值都小于256,我想知道使用字符串而不是列表会更快,或者可能是array.array个字节,但是没有骰子:

string: 0.25 s.
arr: 0.21 s.

使用dict进行查找是另一种可能性,利用仅检查2的幂的方式:

log2s_map=dict([(1<<x,x) for x in range(max_log2+1)])

def map(v):
    return log2s_map[v]

但结果并不是那么好:

map: 0.20 s.

只是为了好玩,人们还可以在float对象上使用hex方法来获取一个字符串,该字符串包含(作为其最后一部分)数字的基数2指数。一般来说,提取起来有点慢,但如果指数只是一位数,那么它可以直接完成:

def floathex(v):
    return ord(float(v).hex()[-1])-48

这纯粹是为了娱乐价值,因为它是无竞争性的 - 但令人惊讶的是,仍然比按位方法更快。

所以看起来使用列表是可行的方法。

(由于内存有限,这种方法无法无限扩展,但为了弥补执行速度不依赖于max_log2或输入值,你会注意到的任何方式当运行python代码。关于内存消耗,如果我正确地记住了我的python内部,那么表将占用大约(1<<max_log2)*4个字节,因为内容都是解释器将自动实习的小整数。所以,当{{ 1}}是20,大概是4MB。)

答案 5 :(得分:1)

这实际上是对mac发布的性能测试的评论。我发布这个作为答案,以正确的代码格式和缩进

mac,你能尝试一下Mark Byers建议的bitseach的展开实现吗?也许只是数组访问减慢了它。从理论上讲,这种方法应该比其他方法更快。

它看起来像这样,虽然我不确定格式是否适合python,但我想你可以看到它应该做什么。

def bitwise(v):
    r = 0;
    if( v > 0xffff ) : v >>= 16; r = 16;
    if( v > 0x00ff ) : v >>=  8; r += 8;
    if( v > 0x000f ) : v >>=  4; r += 4;
    if( v > 0x0003 ) : v >>=  2; r += 2;
    return r + ( v >> 1 );

如果python共享java缺少未加工的整数,那么它需要是这样的:

def bitwise(v):
    r = 0;
    if( v & 0xffff0000 ) : v >>>= 16; r = 16;
    if( v > 0x00ff ) : v >>=  8; r += 8;
    if( v > 0x000f ) : v >>=  4; r += 4;
    if( v > 0x0003 ) : v >>=  2; r += 2;
    return r + ( v >> 1 );

答案 6 :(得分:1)

迟到聚会了,int.bit_length(n) - 1怎么样?您要求简单明了,这对我来说似乎是最简单的。 CPython实现看起来性能合理。