用cython加速python代码

用cython加速python代码,python,optimization,cython,Python,Optimization,Cython,我有一个函数,它基本上只是对一个简单定义的散列函数进行大量调用,并测试它何时找到一个重复的函数。我需要用它做很多模拟,所以我希望它尽可能快。我正试图用cython来做这件事。cython代码当前使用一个普通的python整数列表进行调用,这些整数的值在0到m^2之间 import math, random cdef int a,b,c,d,m,pos,value, cyclelimit, nohashcalls def h3(int a,int b,int c,int d, int m,i

我有一个函数,它基本上只是对一个简单定义的散列函数进行大量调用,并测试它何时找到一个重复的函数。我需要用它做很多模拟,所以我希望它尽可能快。我正试图用cython来做这件事。cython代码当前使用一个普通的python整数列表进行调用,这些整数的值在0到m^2之间

import math, random
cdef int a,b,c,d,m,pos,value, cyclelimit, nohashcalls   
def h3(int a,int b,int c,int d, int m,int x):
    return (a*x**2 + b*x+c) %m    
def floyd(inputx):
    dupefound, nohashcalls = (0,0)
    m = len(inputx)
    loops = int(m*math.log(m))
    for loopno in xrange(loops):
        if (dupefound == 1):
            break
        a = random.randrange(m)
        b = random.randrange(m)
        c = random.randrange(m)
        d = random.randrange(m)
        pos = random.randrange(m)
        value = inputx[pos]
        listofpos = [0] * m
        listofpos[pos] = 1
        setofvalues = set([value])
        cyclelimit = int(math.sqrt(m))
        for j in xrange(cyclelimit):
            pos = h3(a,b, c,d, m, inputx[pos])
            nohashcalls += 1    
            if (inputx[pos] in setofvalues):
                if (listofpos[pos]==1):
                    dupefound = 0
                else:
                    dupefound = 1
                    print "Duplicate found at position", pos, " and value", inputx[pos]
                break
            listofpos[pos] = 1
            setofvalues.add(inputx[pos])
    return dupefound, nohashcalls 
如何将inputx和ListoPhos转换为使用C类型数组并以C速度访问数组?我还可以使用其他的加速吗?可以加快值的设置吗

因此有一些东西可以比较,在我的计算机上,50个m=5000的floyd()调用目前需要30秒左右

更新:显示如何调用floyd的示例代码段

m = 5000
inputx = random.sample(xrange(m**2), m)
(dupefound, nohashcalls) = edcython.floyd(inputx)

您需要使用这个特定的哈希算法吗?为什么不为dict使用内置的哈希算法呢?例如:

from collections import Counter
cnt = Counter(inputx)
dupes = [k for k, v in cnt.iteritems() if v > 1]

首先,似乎必须在函数中键入变量

其次,
cython-a
“annotate”的意思是对cython编译器生成的代码进行非常出色的分解,并用颜色编码表示它有多脏(读:pythonapi重)。在尝试优化任何内容时,此输出非常重要

第三,上现在很有名的一页解释了如何快速、C风格地访问Numpy数组数据。不幸的是,它冗长而烦人。不过,我们很幸运,因为最近的Cython提供了一款既易于使用又非常棒的手机。在你尝试做任何其他事情之前,先阅读整页

大约十分钟后,我想到了这个:

# cython: infer_types=True

# Use the C math library to avoid Python overhead.
from libc cimport math
# For boundscheck below.
import cython
# We're lazy so we'll let Numpy handle our array memory management.
import numpy as np
# You would normally also import the Numpy pxd to get faster access to the Numpy
# API, but it requires some fancier compilation options so I'll leave it out for
# this demo.
# cimport numpy as np

import random

# This is a small function that doesn't need to be exposed to Python at all. Use
# `cdef` instead of `def` and inline it.
cdef inline int h3(int a,int b,int c,int d, int m,int x):
    return (a*x**2 + b*x+c) % m

# If we want to live fast and dangerously, we tell cython not to check our array
# indices for IndexErrors. This means we CAN overrun our array and crash the
# program or screw up our stack. Use with caution. Profiling suggests that we
# aren't gaining anything in this case so I leave it on for safety.
# @cython.boundscheck(False)
# `cpdef` so that calling this function from another Cython (or C) function can
# skip the Python function call overhead, while still allowing us to use it from
# Python.
cpdef floyd(int[:] inputx):
    # Type the variables in the scope of the function.
    cdef int a,b,c,d, value, cyclelimit
    cdef unsigned int dupefound = 0
    cdef unsigned int nohashcalls = 0
    cdef unsigned int loopno, pos, j

    # `m` has type int because inputx is already a Cython memory view and
    # `infer-types` is on.
    m = inputx.shape[0]

    cdef unsigned int loops = int(m*math.log(m))

    # Again using the memory view, but letting Numpy allocate an array of zeros.
    cdef int[:] listofpos = np.zeros(m, dtype=np.int32)

    # Keep this random sampling out of the loop
    cdef int[:, :] randoms = np.random.randint(0, m, (loops, 5)).astype(np.int32)

    for loopno in range(loops):
        if (dupefound == 1):
            break

        # From our precomputed array
        a = randoms[loopno, 0]
        b = randoms[loopno, 1]
        c = randoms[loopno, 2]
        d = randoms[loopno, 3]
        pos = randoms[loopno, 4]

        value = inputx[pos]

        # Unforunately, Memory View does not support "vectorized" operations
        # like standard Numpy arrays. Otherwise we'd use listofpos *= 0 here.
        for j in range(m):
            listofpos[j] = 0

        listofpos[pos] = 1
        setofvalues = set((value,))
        cyclelimit = int(math.sqrt(m))
        for j in range(cyclelimit):
            pos = h3(a, b, c, d, m, inputx[pos])
            nohashcalls += 1
            if (inputx[pos] in setofvalues):
                if (listofpos[pos]==1):
                    dupefound = 0
                else:
                    dupefound = 1
                    print "Duplicate found at position", pos, " and value", inputx[pos]
                break
            listofpos[pos] = 1
            setofvalues.add(inputx[pos])
    return dupefound, nohashcalls
这里没有什么技巧是没有解释的,这是我自己学会的,但有助于看到所有这些技巧的结合

对原始代码最重要的更改出现在注释中,但它们都相当于给Cython提示如何生成不使用Python API的代码

顺便说一句:我真的不知道为什么默认情况下
infer_types
没有打开。它让编译器 在可能的情况下,隐式地使用C类型而不是Python类型,这意味着您的工作量会减少

如果在此基础上运行
cython-a
,您将看到调用Python的唯一行是对random.sample的调用,以及对Python集()的构建或添加

在我的机器上,您的原始代码在2.1秒内运行。我的版本运行时间为0.6秒

下一步是从该循环中获取random.sample,但我将把它留给您


我编辑了我的答案,以演示如何预计算兰德样本。这将使时间缩短到0.4秒

您是否想过添加一种机制来记忆过去的结果?我发现对
hash
方法的调用可能会出现重叠,这可能会大大加快算法的速度,但会牺牲内存空间。你是说存储h3的结果吗?函数在发现重复项时立即停止,因此这似乎没有帮助。我怀疑主要的加速将来自使用C类型数组,但我不确定如何做到这一点。
floyd
的确切输入是什么?我假设只有一个整数的
列表。m=5000。inputx=随机样本(xrange(m**2),m)。(dupefound,nohashcalls)=edcython.floyd(inputx)。我知道。事实上,我将使用一些自制的哈希函数。谢谢,这真的很有帮助。a、b、c、d变量需要在for循环的每次迭代中重新采样,因此不能预先计算,但可能可以使用c的rand()来代替。是的,但是
m
是固定的,您知道您需要每个循环的
样本。我可能会使用
numpy.random.randint(0,m,size=loops)
,然后索引到其中。一些基准测试还表明
cython.boundscheck(False)
没有加快任何速度,所以出于安全考虑,我将其注释掉。在正常情况下,您确实需要boundscheck。只有在您的代码完成并测试之后才关闭它,即使是在测试之后,也只有当它在基准测试时有真正的影响时才关闭它。谢谢。我也在cython 0.15中尝试过你的代码,但它似乎不理解[:]符号,这在0.16中似乎是新的。是的,我认为它们是一个相对较新的特性。它们是首选的,因为它们只要求传入数据实现PEP 3118中指定的缓冲区,但如果您仅使用numpy阵列,则可以使用numpy api并完成
cdefint[:]foo=…
变成
cdefnp.ndarray(int,ndim=1)foo=…