Создание чередующегося буфера для pyopengl и numpy - PullRequest
7 голосов
/ 28 февраля 2010

Я пытаюсь собрать несколько вершин и текстурных координат в чередующемся массиве перед отправкой его в glInterleavedArrays / glDrawArrays pyOpengl. Единственная проблема заключается в том, что я не могу найти достаточно быстрый способ добавления данных в массив.

Есть ли лучший способ сделать это? Я бы подумал, что было бы быстрее предварительно выделить массив, а затем заполнить его данными, но вместо этого создание списка Python и преобразование его в массив Numpy "быстрее". Хотя 15мс для 4096 квадратов кажутся медленными.

Я включил пример кода и его время.

#!/usr/bin/python

import timeit
import numpy
import ctypes
import random

USE_RANDOM=True
USE_STATIC_BUFFER=True

STATIC_BUFFER = numpy.empty(4096*20, dtype=numpy.float32)

def render(i):
    # pretend these are different each time
    if USE_RANDOM:
        tex_left, tex_right, tex_top, tex_bottom = random.random(), random.random(), random.random(), random.random()
        left, right, top, bottom = random.random(), random.random(), random.random(), random.random()
    else:
        tex_left, tex_right, tex_top, tex_bottom = 0.0, 1.0, 1.0, 0.0
        left, right, top, bottom = -1.0, 1.0, 1.0, -1.0

    ibuffer = (
        tex_left, tex_bottom,   left, bottom, 0.0,  # Lower left corner
        tex_right, tex_bottom,  right, bottom, 0.0, # Lower right corner
        tex_right, tex_top,     right, top, 0.0,    # Upper right corner
        tex_left, tex_top,      left, top, 0.0,     # upper left
    )

    return ibuffer



# create python list.. convert to numpy array at end
def create_array_1():
    ibuffer = []
    for x in xrange(4096):
        data = render(x)
        ibuffer += data

    ibuffer = numpy.array(ibuffer, dtype=numpy.float32)
    return ibuffer

# numpy.array, placing individually by index
def create_array_2():
    if USE_STATIC_BUFFER:
        ibuffer = STATIC_BUFFER
    else:
        ibuffer = numpy.empty(4096*20, dtype=numpy.float32)
    index = 0
    for x in xrange(4096):
        data = render(x)
        for v in data:
            ibuffer[index] = v
            index += 1
    return ibuffer

# using slicing
def create_array_3():
    if USE_STATIC_BUFFER:
        ibuffer = STATIC_BUFFER
    else:
        ibuffer = numpy.empty(4096*20, dtype=numpy.float32)
    index = 0
    for x in xrange(4096):
        data = render(x)
        ibuffer[index:index+20] = data
        index += 20
    return ibuffer

# using numpy.concat on a list of ibuffers
def create_array_4():
    ibuffer_concat = []
    for x in xrange(4096):
        data = render(x)
        # converting makes a diff!
        data = numpy.array(data, dtype=numpy.float32)
        ibuffer_concat.append(data)
    return numpy.concatenate(ibuffer_concat)

# using numpy array.put
def create_array_5():
    if USE_STATIC_BUFFER:
        ibuffer = STATIC_BUFFER
    else:
        ibuffer = numpy.empty(4096*20, dtype=numpy.float32)
    index = 0
    for x in xrange(4096):
        data = render(x)
        ibuffer.put( xrange(index, index+20), data)
        index += 20
    return ibuffer

# using ctype array
CTYPES_ARRAY = ctypes.c_float*(4096*20)
def create_array_6():
    ibuffer = []
    for x in xrange(4096):
        data = render(x)
        ibuffer += data
    ibuffer = CTYPES_ARRAY(*ibuffer)
    return ibuffer

def equals(a, b):

    for i,v in enumerate(a):
        if b[i] != v:
            return False
    return True



if __name__ == "__main__":
    number = 100

    # if random, don't try and compare arrays
    if not USE_RANDOM and not USE_STATIC_BUFFER:
        a =  create_array_1()
        assert equals( a, create_array_2() )
        assert equals( a, create_array_3() )
        assert equals( a, create_array_4() )
        assert equals( a, create_array_5() )
        assert equals( a, create_array_6() )

    t = timeit.Timer( "testing2.create_array_1()", "import testing2" )
    print 'from list:', t.timeit(number)/number*1000.0, 'ms'

    t = timeit.Timer( "testing2.create_array_2()", "import testing2" )
    print 'array: indexed:', t.timeit(number)/number*1000.0, 'ms'

    t = timeit.Timer( "testing2.create_array_3()", "import testing2" )
    print 'array: slicing:', t.timeit(number)/number*1000.0, 'ms'

    t = timeit.Timer( "testing2.create_array_4()", "import testing2" )
    print 'array: concat:', t.timeit(number)/number*1000.0, 'ms'

    t = timeit.Timer( "testing2.create_array_5()", "import testing2" )
    print 'array: put:', t.timeit(number)/number*1000.0, 'ms'

    t = timeit.Timer( "testing2.create_array_6()", "import testing2" )
    print 'ctypes float array:', t.timeit(number)/number*1000.0, 'ms'

Сроки с использованием случайных чисел:

$ python testing2.py
from list: 15.0486779213 ms
array: indexed: 24.8184704781 ms
array: slicing: 50.2214789391 ms
array: concat: 44.1691994667 ms
array: put: 73.5879898071 ms
ctypes float array: 20.6674289703 ms

редактирование примечания: изменен код для создания случайных чисел для каждого рендера, чтобы уменьшить повторное использование объекта и имитировать разные вершины каждый раз.

редактировать note2: добавлен статический буфер и заставить все numpy.empty () использовать dtype = float32

примечание 1 / апр / 2010: по-прежнему нет прогресса, и я действительно не чувствую, что какой-либо из ответов еще решил проблему.

Ответы [ 3 ]

1 голос
/ 02 марта 2010

Преимущество numpy не реализуется простым хранением данных в массиве, оно достигается путем выполнения операций над многими элементами в массиве вместо одного за другим. Ваш пример может быть сведен к минимуму и оптимизирован для этого тривиального решения с ускорением на порядки:

numpy.random.standard_normal(4096*20)

... это не очень полезно, но намекает на то, где расходы.

Это постепенное улучшение, которое превосходит решение по добавлению в список (но незначительно), исключая итерацию для 4096 элементов.

xs = numpy.arange(4096)
render2 = numpy.vectorize(render)

def create_array_7():
    ibuffer = STATIC_BUFFER
    for i, a in enumerate(render2(xs)):
        ibuffer[i::20] = a
    return ibuffer

... но не ускорение, которое мы ищем.

Реальная экономия будет достигнута путем пересмотра процедуры рендеринга, так что вам не нужно будет создавать объект python для каждого значения, которое в итоге будет помещено в буфер. При чем здесь tex_left, tex_right ... и т. Д. родом из? Они рассчитаны или прочитаны?

1 голос
/ 28 февраля 2010

Причина, по которой create_array_1 намного быстрее, заключается в том, что все элементы в списке (python) указывают на один и тот же объект. Вы можете увидеть это, если протестируете:

print (ibuffer[0] is ibuffer[1])

внутри подпрограмм. В create_array_1 это верно (перед созданием массива numpy), в то время как в create_array_2 это всегда будет false. Я предполагаю, что это означает, что шаг преобразования данных в преобразовании массива должен произойти только один раз в create_array_1, тогда как в create_array_2 это происходит 4096 раз.

Если это причина, я думаю, что время будет другим, если вы сделаете рендер генерировать случайные данные. Create_array_5 самый медленный, поскольку он создает новый массив каждый раз, когда вы добавляете данные в конец.

0 голосов
/ 01 марта 2010

Я знаю, это кажется странным, но вы пробовали fromfile?

...