在Python中从浮点元组列表构建C数组的最快方法是什么?

6 投票
5 回答
1479 浏览
提问于 2025-04-16 06:55

背景:我的Python代码将二维顶点数组传递给OpenGL。

我测试了两种方法,一种是用ctypes,另一种是用struct,后者的速度快了两倍多。

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])

还有其他的替代方案吗?有没有什么建议可以加速这段代码(没错,这确实是我代码中的一个瓶颈)?

5 个回答

1

我碰到一个新的想法。现在没时间去测试它,但如果有人有时间的话,可以试试:

 # 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数组,但不填充它。然后我们用切片的方式来填充它。比我聪明的人告诉我,这样给切片赋值可能会提高性能。这样做可以让我们直接在赋值的右边使用一个列表或可迭代对象,而不需要用*iterable的语法,这样会避免对可迭代对象进行一些中间处理。我猜这就是在创建pyglet的Batches时发生的事情。

你可以只创建一次c_array,然后每次points列表改变时,只需重新赋值(上面代码的最后一行)。

可能还有另一种写法,可以接受原始的points定义(一个包含(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)
3

你可以把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%。

2

你可以试试Cython。对我来说,这样做的效果是:

function       usec per loop:
               Python  Cython
array_ctypes   1370    1220
array_struct    384     249
array_numpy     336     339

在我的硬件上(一台运行Windows XP的老笔记本),Numpy的性能提升只有15%,而Cython的提升大约是35%(而且在你的分发代码中不需要额外的依赖)。

如果你可以放宽要求,不一定要每个点都是浮点数的元组,可以把'points'简单地做成一个扁平的浮点数列表:

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代码,以及哪些部分已经转换成纯C代码(白色)。这就是我把上面的代码分成这么多行的原因,因为颜色是按行显示的,分开写可以帮助你更好地理解。)

完整的Cython使用说明在这里: http://docs.cython.org/src/quickstart/build.html

Cython可能会在你的整个代码库中带来类似的性能提升,在理想情况下,应用适当的静态类型后,速度可以提高十倍甚至一百倍。

撰写回答