[英]What is the fastest way in python to build a c array from a list of tuples of floats?
上下文:我的Python代码将2D顶点数组传递给OpenGL。
我测试了两种方法,一种是ctypes,另一种是结构,后者是两倍以上。
from random import random
points = [(random(), random()) for _ in xrange(1000)]
from ctypes import c_float
def array_ctypes(points):
n = len(points)
return n, (c_float*(2*n))(*[u for point in points for u in point])
from struct import pack
def array_struct(points):
n = len(points)
return n, pack("f"*2*n, *[u for point in points for u in point])
还有其他选择吗? 有关如何加速此类代码的任何提示(是的,这是我的代码的一个瓶颈)?
您可以将numpy数组传递给PyOpenGL而不会产生任何开销。 (numpy数组的data
属性是一个缓冲区,指向底层C数据结构,它包含与您正在构建的数组相同的信息)
import numpy as np
def array_numpy(points):
n = len(points)
return n, np.array(points, dtype=np.float32)
在我的计算机上,这比基于struct
的方法快约40%。
你可以试试Cython。 对我来说,这给了:
function usec per loop:
Python Cython
array_ctypes 1370 1220
array_struct 384 249
array_numpy 336 339
因此Numpy仅在我的硬件(运行WindowsXP的旧笔记本电脑)上获得15%的好处,而Cython提供约35%(在您的分布式代码中没有任何额外的依赖性)。
如果你可以放松你的要求,每个点是一个浮点元组,只需将'点'作为一个扁平的浮点列表:
def array_struct_flat(points):
n = len(points)
return pack(
"f"*n,
*[
coord
for coord in points
]
)
points = [random() for _ in xrange(1000 * 2)]
然后结果输出是相同的,但时间进一步下降:
function usec per loop:
Python Cython
array_struct_flat 157
如果比我聪明的人想要在代码中添加静态类型声明,那么Cython可能比这更好。 (运行'cython -a test.pyx'对此非常有用,它会生成一个html文件,显示代码中最慢(黄色)普通Python的位置,而python已转换为纯C(白色)。这就是为什么我将上面的代码分散到这么多行上,因为着色是按行完成的,所以它有助于将它扩展出来。)
完整的Cython说明如下: http ://docs.cython.org/src/quickstart/build.html
Cython可能会在整个代码库中产生类似的性能优势,并且在理想条件下,通过应用适当的静态类型,可以将速度提高十倍或一百倍。
如果性能是一个问题,您不希望将ctypes数组与star操作一起使用(例如, (ctypes.c_float * size)(*t)
)。
在我的测试pack
中最快,然后使用带有地址转换的array
模块(或使用from_buffer函数)。
import timeit
repeat = 100
setup="from struct import pack; from random import random; import numpy; from array import array; import ctypes; t = [random() for _ in range(2* 1000)];"
print(timeit.timeit(stmt="v = array('f',t); addr, count = v.buffer_info();x = ctypes.cast(addr,ctypes.POINTER(ctypes.c_float))",setup=setup,number=repeat))
print(timeit.timeit(stmt="v = array('f',t);a = (ctypes.c_float * len(v)).from_buffer(v)",setup=setup,number=repeat))
print(timeit.timeit(stmt='x = (ctypes.c_float * len(t))(*t)',setup=setup,number=repeat))
print(timeit.timeit(stmt="x = pack('f'*len(t), *t);",setup=setup,number=repeat))
print(timeit.timeit(stmt='x = (ctypes.c_float * len(t))(); x[:] = t',setup=setup,number=repeat))
print(timeit.timeit(stmt='x = numpy.array(t,numpy.float32).data',setup=setup,number=repeat))
在我的测试中,array.array方法比Jonathan Hartley的方法略快,而numpy方法的速度只有一半:
python3 convert.py
0.004665990360081196
0.004661010578274727
0.026358536444604397
0.0028003649786114693
0.005843495950102806
0.009067213162779808
净赢家是包。
我偶然发现了另一个想法。 我现在没有时间对其进行分析,但万一其他人这样做:
# untested, but I'm fairly confident it runs
# using 'flattened points' list, i.e. a list of n*2 floats
points = [random() for _ in xrange(1000 * 2)]
c_array = c_float * len(points * 2)
c_array[:] = points
也就是说,首先我们创建ctypes数组但不填充它。 然后我们使用切片表示法填充它。 人们比我告诉我更聪明,分配到这样的切片可能有助于提高性能。 它允许我们直接在赋值的RHS上传递列表或迭代,而不必使用* iterable语法,这将执行迭代的一些中间争论。 我怀疑这是创建pyglet的批次的深度。
大概你可以创建一次c_array,然后每次点列表更改时重新分配给它(上面代码中的最后一行)。
可能有一个替代的公式接受点的原始定义((x,y)元组的列表。)像这样:
# very untested, likely contains errors
# using a list of n tuples of two floats
points = [(random(), random()) for _ in xrange(1000)]
c_array = c_float * len(points * 2)
c_array[:] = chain(p for p in points)
你可以使用数组 (还要注意生成器表达式而不是列表推导):
array("f", (u for point in points for u in point)).tostring()
另一个优化是保持点从一开始就变平。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.