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