Какой самый быстрый способ в Python для построения массива c из списка кортежей с плавающей точкой? - PullRequest
6 голосов
/ 11 ноября 2010

Контекст: мой код Python передает массивы 2D вершин в 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])

Любая другая альтернатива? Любой намек на то, как ускорить такой код (и да, это одно из узких мест моего кода)?

Ответы [ 5 ]

3 голосов
/ 11 ноября 2010

Вы можете передавать пустые массивы в PyOpenGL без дополнительных затрат.(Атрибут data массива numpy является буфером, который указывает на базовую структуру данных C, которая содержит ту же информацию, что и создаваемый вами массив)

import numpy as np  
def array_numpy(points):
    n = len(points)
    return n, np.array(points, dtype=np.float32)

На моем компьютере это примерноНа 40% быстрее, чем подход на основе struct.

2 голосов
/ 12 ноября 2010

Вы можете попробовать Cython. Для меня это дает:

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

Таким образом, Numpy дает только 15% преимущества на моем оборудовании (старый ноутбук под управлением WindowsXP), тогда как 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 может обеспечить аналогичные преимущества производительности для всей кодовой базы, и в идеальных условиях при правильной статической типизации может повысить скорость в десять или сто раз.

1 голос
/ 30 августа 2016

Если производительность является проблемой, вы не хотите использовать массивы 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 немного быстрее, чем подход Джонатана Хартли в моем тесте, в то время как у однополосного подхода примерно половина скорости:

python3 convert.py
0.004665990360081196
0.004661010578274727
0.026358536444604397
0.0028003649786114693
0.005843495950102806
0.009067213162779808

Чистый победитель - стая.

1 голос
/ 23 ноября 2010

Есть еще одна идея, с которой я столкнулся.У меня нет времени профилировать это прямо сейчас, но в случае, если кто-то другой делает:

 # 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)
0 голосов
/ 11 ноября 2010

Вы можете использовать массив (обратите внимание также на выражение генератора вместо понимания списка):

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

Другая оптимизация состояла бы в том, чтобы точки сплющивались с самого начала.

...