簡體   English   中英

python從浮點元組列表中構建c數組的最快方法是什么?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM