【问题标题】:Building an interleaved buffer for pyopengl and numpy为 pyopengl 和 numpy 构建交错缓冲区
【发布时间】:2011-01-21 22:46:09
【问题描述】:

我正在尝试在交错数组中批量处理一堆顶点和纹理坐标,然后再将其发送到 pyOpengl 的 glInterleavedArrays/glDrawArrays。唯一的问题是我无法找到足够快的方法将数据附加到 numpy 数组中。

有没有更好的方法来做到这一点?我原以为预先分配数组然后用数据填充它会更快,但是生成python列表并将其转换为numpy数组会“更快”。虽然 4096 个四边形的 15 毫秒似乎很慢。

我已经包含了一些示例代码及其时间安排。

#!/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

note 1/Apr/2010:仍然没有进展,我真的不觉得任何答案都解决了问题。

【问题讨论】:

    标签: python opengl numpy 2d pyopengl


    【解决方案1】:

    create_array_1 快得多的原因似乎是(python)列表中的项目都指向同一个对象。如果您进行测试,您可以看到这一点:

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

    在子程序中。在 create_array_1 中,这是真的(在创建 numpy 数组之前),而在 create_array_2 中,这总是错误的。我想这意味着数组转换中的数据转换步骤只需要在 create_array_1 中发生一次,而在 create_array_2 中发生 4096 次。

    如果是这个原因,我猜如果你让渲染生成随机数据,时间会有所不同。 Create_array_5 最慢,因为每次将数据添加到末尾时它都会创建一个新数组。

    【讨论】:

    • 好点!我的目标是产生可预测的输出,这样我就可以确保它们都正常工作。我添加了一个开关来强制 render() 生成随机数据,而 create_array_1 仍然是最快的。
    • 这让我很困惑。可能还有两件事要检查:如果将 dtype=float32 添加到 numpy.empty 调用中,是否会发生任何变化? order='C' 或 order='F' 重要吗?据我所知,这些不会改变任何事情,但我已经惊讶过一次了。
    • 添加 dtype=float32 似乎没有帮助,强制 create_array_x 共享相同的缓冲区也没有帮助,这表明修改 numpy 数组中的值可能很慢。
    【解决方案2】:

    我知道这看起来很奇怪,但你试过fromfile吗?

    【讨论】:

    • 这可能是动态数据。您的意思是将静态数据保存到磁盘并在需要时重新加载?或者创建一些类似接口的文件并将其传递给 numpy.fromfile ?
    • 我认为这两种选择都是可能的。您不需要创建类似接口的文件 - StringIO 就足够了。
    【解决方案3】:

    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...等在哪里?来自?它们是计算出来的还是读出来的?

    【讨论】:

    • Tex 坐标是基于 tile 索引生成的,但它们可以缓存为 numpy 数组。不过,顶点可能每帧都会更新。另外,我认为顶点和 tex 坐标也不必交错。
    • 哦,查看 numpy 的索引,我了解到您可以使用数组作为另一个数组的索引。例如: >>> tiles = numpy.array( [0,1,1,1,1,2,2,2,3,3,3] ) >>> texcoords = numpy.array( [ [33,20] , [44,50], [55,60], [66,70], [77,80] ) >>> texcoords[tiles] 数组([[33, 20], [44, 50], [44 , 50], [44, 50], [44, 50], [55, 60], [55, 60], [55, 60], [66, 70], [66, 70], [66, 70] ]])
    猜你喜欢
    • 1970-01-01
    • 2021-05-06
    • 1970-01-01
    • 2011-10-29
    • 2018-06-19
    • 2012-05-08
    • 2015-05-15
    • 2012-01-20
    • 1970-01-01
    相关资源
    最近更新 更多