【问题标题】:What is the fastest way in python to build a c array from a list of tuples of floats?python中从浮点元组列表构建c数组的最快方法是什么?
【发布时间】:2011-05-08 14:20:55
【问题描述】:

上下文:我的 Python 代码将二维顶点数组传递给 OpenGL。

我测试了 2 种方法,一种使用 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])

还有其他选择吗? 关于如何加速此类代码的任何提示(是的,这是我的代码的瓶颈之一)?

【问题讨论】:

  • 我也将这个问题交叉发布到新闻组 gmane.comp.python.opengl.user,它返回了类似的答案。

标签: python ctypes pyopengl


【解决方案1】:

您可以将 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%。

【讨论】:

  • 令人印象深刻!我不想将 numpy 依赖项添加到我的代码中,但看起来值得。 (旁注:不指定 dtype 参数会使性能降低 10 倍)
  • 可以通过预先创建 numpy 数组,然后每帧根据需要更新元素来进一步改进这种技术。我在想象顶点大部分是静态的,但有时其中一部分需要更新动画的情况。
  • 一旦存在数组,您还可以从使用 numpy 操作数组中获得额外的好处。例如您可以将一组速度添加到一组位置。这对于粒子系统之类的东西可能特别有用,您的 Python 代码不需要频繁访问结果位置的值。
【解决方案2】:

你可以试试 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 所在的位置,而不是已转换为纯 C(白色)的 Python。这就是为什么我将上面的代码分散到很多行上,因为着色是每行完成的,所以这样分散是有帮助的。)

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

Cython 可能会在您的整个代码库中产生类似的性能优势,并且在理想条件下,应用适当的静态类型,可以将速度提高十倍或一百倍。

【讨论】:

    【解决方案3】:

    我偶然发现了另一个想法。我现在没有时间对其进行分析,但万一其他人这样做:

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

    【讨论】:

    • 感谢@DanielLemire 的反馈。出于兴趣,您是否尝试过此答案中建议的两种方法?
    【解决方案4】:

    如果性能是一个问题,您不希望将 ctypes 数组与星号操作一起使用(例如,(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
    

    净赢家是包。

    【讨论】:

    • 太棒了。可重复的比较测量 - 页面上的最佳答案。
    • 使用 timeit(number=10),我看到时间顺序有相当多的变化。我必须先将它增加到 1000,然后才能稳定下来,形成一个相当一致的顺序。
    • 使用 Daniel 的脚本,我还测量 pack 是最快的条目之一。但是,我很好奇,因为调用“pack”示例中的*t 语法意味着列表“t”被解压缩到一个元组中,用于“pack”的参数。听起来这里仍然存在一些低效率,因此可能会有所改进。
    • Daniel 出色的脚本中有许多 sn-ps,可以通过将尽可能多的内容提取到“设置”部分来节省一点额外的时间。例如,创建格式字符串'f' * len(floats) 可以在设置中完成。更进一步,一些条目也可以通过在设置中分配 C 数组来改进,稍后再填充它。
    • 一个新条目,对我来说,它花费的时间不到任何其他条目的 66%:SETUP="import cffi; import random; floats = [random.random() for _ in range(2 * {0})]; ffi = cffi.FFI(); x = ffi.new('float[]', len(floats)),然后填充数组的代码是 x[0:2000] = floats
    【解决方案5】:

    您可以使用array(还要注意生成器表达式而不是列表推导式):

    array("f", (u for point in points for u in point)).tostring()
    

    另一个优化是从一开始就保持点变平。

    【讨论】:

    • 我在第一次尝试中尝试了生成器,结果证明它会减慢功能。
    • (它也会减慢这个数组版本的速度)。顺便说一句,即使使用列表理解,基于数组的解决方案仍然比结构版本慢 20%...
    猜你喜欢
    • 1970-01-01
    • 2018-12-05
    • 2015-11-26
    • 1970-01-01
    • 2013-06-25
    • 1970-01-01
    • 1970-01-01
    • 2016-12-20
    • 1970-01-01
    相关资源
    最近更新 更多