二进制1d Numpy数组到整数的最快一对一映射

时间:2017-01-12 16:16:50

标签: python performance numpy mapping cython

将仅包含01 s的1d Numpy数组转换为唯一整数的最快方法是什么?

到目前为止,我提出的最好的方法是使用Cython并将数组视为镜像二进制数*。

@cython.boundscheck(False)
@cython.wraparound(False)
def _map_binary(np.ndarray[np.int64_t, ndim=1] x):

    cdef int tot = 0
    cdef int i
    cdef int n = x.shape[0]

    for i in xrange(n):
        if x[i]:
            tot += 2**i
    return tot

由于这仍然是我的算法的瓶颈,我想知道是否有更智能,更快捷的方法。

*显然这种映射只对于具有相同长度的数组是一对一的(因为对数组附加零不会改变结果整数),但这对我来说是好的。

2 个答案:

答案 0 :(得分:3)

整理评论和我自己的一些想法

一些纯Python(+ numpy)选项:

import numpy as np

def _map_binary_str_and_back(x):
    # from comment by @NickA
    return int("".join([str(c) for c in x]),2)

def _map_binary_np_dot(x):
    # from question http://stackoverflow.com/questions/41069825/convert-binary-01-numpy-to-integer-or-binary-string
    return np.dot(x,1 << np.arange(x.size))

def _map_binary_np_pack(x):
    # uses built in numpy function packbits, but unfortunately needs a bit of manipulation
    # afterwards
    x = np.packbits(x) # as np.int8
    x.resize((8,),refcheck=False)
    return x.view(dtype=np.int64)

一些Cython选项(请注意,我已将输出更改为64位整数,以便它适用于最多64个元素的数组):

cimport cython
cimport numpy as np

def _map_binary_original(np.ndarray[np.int64_t, ndim=1] x):

    cdef np.uint64_t tot = 0
    cdef np.uint64_t i
    cdef int n = x.shape[0]

    for i in xrange(n):
        if x[i]:
            tot += 2**i
    return tot

@cython.boundscheck(False)
@cython.wraparound(False)
def _map_binary_contig(np.ndarray[np.int64_t, ndim=1, mode="c"] x):

    cdef np.uint64_t tot = 0
    cdef np.uint64_t i
    cdef int n = x.shape[0]

    for i in xrange(n):
        if x[i]:
            tot += 2**i
    return tot

@cython.boundscheck(False)
@cython.wraparound(False)    
def _map_binary_shift(np.ndarray[np.int64_t, ndim=1, mode="c"] x):

    cdef np.uint64_t tot = 0
    cdef np.uint64_t i
    cdef int n = x.shape[0]

    for i in xrange(n):
        if x[i]:
            tot += 1<<i
    return tot

@cython.boundscheck(False)
@cython.wraparound(False)    
def _map_binary_times2(np.ndarray[np.int64_t, ndim=1, mode="c"] x):
    # @FranciscoCouzo

    cdef np.uint64_t tot = 0
    cdef np.uint64_t i
    cdef int n = x.shape[0]

    for i in xrange(n):
        tot *= 2
        if x[i]:            
            tot +=1 
    return tot

@cython.boundscheck(False)
@cython.wraparound(False)    
def _map_binary_times2_as_shift(np.ndarray[np.int64_t, ndim=1, mode="c"] x):

    cdef np.uint64_t tot = 0
    cdef np.uint64_t i
    cdef int n = x.shape[0]

    for i in xrange(n):
        tot *= 2
        if x[i]:            
            tot +=1 
    return tot

和(供参考)一些时序代码

from map_binary import (_map_binary_original,_map_binary_contig,
                        _map_binary_shift,_map_binary_times2,
                        _map_binary_times2_as_shift)

test_array = np.random.randint(2,size=(60,)).astype(dtype=np.int64)

def time_function(name):
    from timeit import timeit

    num = 10000
    timed = timeit("f(x)","from __main__ import {} as f, test_array as x".format(name),number=num)/num
    print(name, timed)

for n in list(globals().keys()):
    if n.startswith('_map_binary'):
        time_function(n)

结果(为了清晰起见,略微重新格式化):

_map_binary_str_and_back    9.774386967484043e-05
_map_binary_np_dot          7.402434574531678e-06
_map_binary_np_pack         1.5813756692768855e-06
_map_binary_original        7.462656716457738e-07
_map_binary_contig          7.208434833198e-07
_map_binary_shift           5.84043665719558e-07
_map_binary_times2          6.467991376011505e-07
_map_binary_times2_as_shift 6.412435894529889e-07

总结:

  • &#34;没有Cython&#34;使用np.packbits的版本是最快的选择(但显然比Cython版本差)。然而,无Cython版本需要进一步的工作,以确保他们给出相同的答案(我认为点是整数溢出。packbits翻转字节序,所以给出一个有效但不同的答案)
  • 指定数组的连续性会使事情变得更快。
  • Shift似乎是最快的,然后是乘法然后是幂(如果使用无符号整数,则移位最快)。

答案 1 :(得分:0)

我不确定您的解决方案是否是算法视角下的最佳方式,但为了编写更优化的Cython代码,我建议遵循以下更改:

  1. 在Cython中使用内存视图数组
  2. 当您使用Cython时使用非pythonic代码,以便Cython可以从您的python代码生成更小的C代码。因此,您可以使用shape而不是使用size()而不是使用xrange和索引,而不是使用i,并且仅在数组上循环索引并增加变量range()在每次迭代中(或者至少只使用xrange),因为pow是一个生成器,需要更多工作才能转换为C.
  3. 使用form ibc.math cimport pow @cython.boundscheck(False) @cython.wraparound(False) cdef int _map_binary(np.int32_t[:] x): cdef int tot = 0 cdef int i = 0 cdef int n = x.size cdef int item for item in x: if item: tot = tot + pow(2, i) i = i + 1 return tot
  4. 等C库
  5. 预定义您的功能
  6. dplyr