2010-11-11 8 views
6

El contexto: mi código Python pasa las matrices de vértices 2D a OpenGL.¿Cuál es la forma más rápida en python para construir una matriz c a partir de una lista de tuplas de flotadores?

Probé 2 enfoques, uno con ctypes, el otro con struct, este último es más de dos veces más rápido.

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

¿Alguna otra alternativa? Cualquier sugerencia sobre cómo acelerar ese código (y sí, este es un cuello de botella de mi código)?

+0

Publiqué esta pregunta al grupo de noticias gmane.comp.python.opengl.user también, que arrojó respuestas similares a las siguientes. –

Respuesta

2

Puede probar Cython. Para mí, esto da:

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

Así Numpy sólo da 15% de beneficios en mi hardware (viejo portátil Windows XP corriendo), mientras que Cython da alrededor del 35% (sin ningún tipo de dependencia adicional en su código distribuido).

Si puede aflojarse el requisito de que cada punto es una tupla de flotadores, y simplemente hacer 'puntos' una lista lineal de flotadores:

def array_struct_flat(points): 
    n = len(points) 
    return pack(
     "f"*n, 
     *[ 
      coord 
      for coord in points 
     ] 
    ) 

points = [random() for _ in xrange(1000 * 2)] 

entonces la salida resultante es el mismo, pero va el tiempo más abajo:

function   usec per loop: 
        Python Cython 
array_struct_flat   157 

Cython podría ser capaz de sustancialmente mejor que esto también, si alguien más inteligente que yo quería añadir declaraciones de tipo estático del código. (Ejecutar 'cython -a test.pyx' es invaluable para esto, produce un archivo html que muestra dónde está el Python plano más lento (amarillo) en su código, frente a python que se ha convertido en C puro (blanco). Es por eso que extendí el código de arriba hacia fuera sobre tantas líneas, debido a que la coloración se realiza por línea, lo que ayuda a difundirlo a cabo por el estilo) instrucciones

completa Cython aquí:. http://docs.cython.org/src/quickstart/build.html

Cython podría producir prestaciones de rendimiento similares en toda la base de código, y en condiciones ideales, con la tipificación estática adecuada aplicada, puede mejorar la velocidad por factores de diez o cien.

0

Puede utilizar array (aviso también la expresión del generador en lugar de la lista por comprensión):

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

Otra optimización sería mantener los puntos aplanados desde el principio.

+0

Intenté generadores en mis primeros intentos, y resulta que ralentiza las funciones. – rndblnch

+0

(y también ralentiza esta versión de matriz). por cierto, incluso con la comprensión de listas, la solución basada en arreglos sigue siendo un 20% más lenta que la versión struct ... – rndblnch

3

Puede pasar matrices numpy a PyOpenGL sin incurrir en gastos generales. (El atributo data de la matriz numpy es una memoria intermedia que apunta a la estructura de datos C subyacente que contiene la misma información que la matriz está la construcción)

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

En mi computadora, esto es un 40% más rápido que el enfoque basado en struct.

+0

¡Impresionante! No quería agregar la dependencia numpy a mi código, pero parece que vale la pena. (nota al margen: no especificar el parámetro dtype mata la perforación por un factor 10) – rndblnch

+0

¿Se puede mejorar aún más esta técnica, creando la matriz numpy por adelantado, y luego simplemente actualizando los elementos como se requiere en cada cuadro? Estoy imaginando situaciones en las que los vértices serían en su mayoría estáticos, pero a veces una parte de ellos necesitaría actualizarse para las animaciones. –

+0

También puede obtener beneficios adicionales al usar numpy para manipular las matrices una vez que existen. p.ej. podría agregar una matriz de velocidades a una matriz de posiciones. Esto podría ser especialmente bueno para cosas como sistemas de partículas, donde su código Python no necesita acceso frecuente al valor de las posiciones resultantes. –

1

Hay otra idea con la que tropecé. No tengo tiempo para el perfil it en este momento, pero en caso de que alguien más lo hace:

# 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 

Es decir, primero creamos la matriz ctypes pero no pueblan. Luego lo poblamos usando la notación de división. La gente es más inteligente de lo que yo digo que la asignación a una porción como esta puede ayudar al rendimiento.Nos permite pasar una lista o iterable directamente en el RHS de la asignación, sin tener que usar la sintaxis * iterable, que realizaría algunas disputas intermedias de la iterable. Sospecho que esto es lo que sucede en las profundidades de la creación de Batches de pigmeo.

Presumiblemente, solo podría crear c_array una vez, luego reasignarlo (la última línea en el código anterior) cada vez que cambie la lista de puntos.

Es probable que haya una formulación alternativa que acepta la definición original de puntos (una lista de, y) tuplas x (.) Algo como esto:

# 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

Gracias por los comentarios @DanielLemire. Fuera de interés, ¿probaste los dos enfoques sugeridos de esta respuesta? –

+0

Ver mi respuesta actualizada a esta pregunta. –

1

Si el rendimiento es un problema, que no quiere usar matrices ctypes con la operación de estrella (por ejemplo, (ctypes.c_float * size)(*t)).

En mi prueba pack es seguido más rápido por el uso del módulo array con un molde de la dirección (o usando la función 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)) 

El enfoque array.array es ligeramente más rápido que el enfoque de Jonathan Hartley en mi prueba, mientras que el enfoque numpy tiene aproximadamente la mitad de la velocidad:

python3 convert.py 
0.004665990360081196 
0.004661010578274727 
0.026358536444604397 
0.0028003649786114693 
0.005843495950102806 
0.009067213162779808 

El ganador neto es el paquete.

+0

fabuloso. Mediciones comparativas reproducibles: la mejor respuesta en la página. –

+0

Con timeit (número = 10), veo una buena cantidad de variación en el orden de los tiempos. Tuve que aumentarlo a 1000 antes de que se establecieran en un orden bastante consistente. –

+0

Usando el guión de Daniel, también mido el paquete para estar entre la entrada más rápida. Sin embargo, tengo curiosidad porque la sintaxis '* t' en el ejemplo de llamada a 'paquete' significa que la lista 't' se está desempaquetando en una tupla, para los argumentos de 'paquete'. Esto parece que todavía hay algo de ineficiencia aquí, por lo que podría mejorarse. –

Cuestiones relacionadas