为类型化内存视图分配内存的推荐方式是什么?

问题描述 投票:0回答:2

有关类型化内存视图的 Cython 文档列出了分配给类型化内存视图的三种方法:

  1. 来自原始 C 指针,
  2. 来自
    np.ndarray
  3. 来自
    cython.view.array

假设我没有从外部将数据传递到我的 cython 函数,而是想要分配内存并将其作为

np.ndarray
返回,我选择哪个选项?还假设该缓冲区的大小不是编译时常量,即我无法在堆栈上分配,但需要
malloc
对于选项 1。

因此,这 3 个选项看起来像这样:

from libc.stdlib cimport malloc, free
cimport numpy as np
from cython cimport view

np.import_array()

def memview_malloc(int N):
    cdef int * m = <int *>malloc(N * sizeof(int))
    cdef int[::1] b = <int[:N]>m
    free(<void *>m)

def memview_ndarray(int N):
    cdef int[::1] b = np.empty(N, dtype=np.int32)

def memview_cyarray(int N):
    cdef int[::1] b = view.array(shape=(N,), itemsize=sizeof(int), format="i")

令我惊讶的是,在所有三种情况下,Cython 都会生成相当多的内存分配代码,特别是对

__Pyx_PyObject_to_MemoryviewSlice_dc_int
的调用。这表明(我在这里可能是错的,我对 Cython 内部工作原理的了解非常有限)它首先创建一个 Python 对象,然后将其“转换”到内存视图中,这似乎是不必要的开销。

一个简单的基准测试并没有揭示这三种方法之间的太大区别,其中 2. 是最快的,微弱优势。

推荐三种方法中的哪一种?或者有其他更好的选择吗?

后续问题:在函数中使用该内存视图之后,我想最终将结果返回为

np.ndarray
。类型化内存视图是最好的选择还是我宁愿使用如下旧的缓冲区接口来首先创建一个
ndarray

cdef np.ndarray[DTYPE_t, ndim=1] b = np.empty(N, dtype=np.int32)
python memory memory-management buffer cython
2个回答
93
投票

请查看这里寻找答案。

基本思想是您想要

cpython.array.array
cpython.array.clone
不是
cython.array.*
):

from cpython.array cimport array, clone

# This type is what you want and can be cast to things of
# the "double[:]" syntax, so no problems there
cdef array[double] armv, templatemv

templatemv = array('d')

# This is fast
armv = clone(templatemv, L, False)

编辑

事实证明该线程中的基准测试是垃圾。这是我的设置,以及我的时间安排:

# cython: language_level=3
# cython: boundscheck=False
# cython: wraparound=False

import time
import sys

from cpython.array cimport array, clone
from cython.view cimport array as cvarray
from libc.stdlib cimport malloc, free
import numpy as numpy
cimport numpy as numpy

cdef int loops

def timefunc(name):
    def timedecorator(f):
        cdef int L, i

        print("Running", name)
        for L in [1, 10, 100, 1000, 10000, 100000, 1000000]:
            start = time.clock()
            f(L)
            end = time.clock()
            print(format((end-start) / loops * 1e6, "2f"), end=" ")
            sys.stdout.flush()

        print("μs")
    return timedecorator

print()
print("INITIALISATIONS")
loops = 100000

@timefunc("cpython.array buffer")
def _(int L):
    cdef int i
    cdef array[double] arr, template = array('d')

    for i in range(loops):
        arr = clone(template, L, False)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("cpython.array memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr
    cdef array template = array('d')

    for i in range(loops):
        arr = clone(template, L, False)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("cpython.array raw C type")
def _(int L):
    cdef int i
    cdef array arr, template = array('d')

    for i in range(loops):
        arr = clone(template, L, False)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("numpy.empty_like memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr
    template = numpy.empty((L,), dtype='double')

    for i in range(loops):
        arr = numpy.empty_like(template)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("malloc")
def _(int L):
    cdef int i
    cdef double* arrptr

    for i in range(loops):
        arrptr = <double*> malloc(sizeof(double) * L)
        free(arrptr)

    # Prevents dead code elimination
    str(arrptr[0])

@timefunc("malloc memoryview")
def _(int L):
    cdef int i
    cdef double* arrptr
    cdef double[::1] arr

    for i in range(loops):
        arrptr = <double*> malloc(sizeof(double) * L)
        arr = <double[:L]>arrptr
        free(arrptr)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("cvarray memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr

    for i in range(loops):
        arr = cvarray((L,),sizeof(double),'d')

    # Prevents dead code elimination
    str(arr[0])



print()
print("ITERATING")
loops = 1000

@timefunc("cpython.array buffer")
def _(int L):
    cdef int i
    cdef array[double] arr = clone(array('d'), L, False)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("cpython.array memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr = clone(array('d'), L, False)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("cpython.array raw C type")
def _(int L):
    cdef int i
    cdef array arr = clone(array('d'), L, False)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("numpy.empty_like memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr = numpy.empty((L,), dtype='double')

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("malloc")
def _(int L):
    cdef int i
    cdef double* arrptr = <double*> malloc(sizeof(double) * L)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arrptr[i]

    free(arrptr)

    # Prevents dead-code elimination
    str(d)

@timefunc("malloc memoryview")
def _(int L):
    cdef int i
    cdef double* arrptr = <double*> malloc(sizeof(double) * L)
    cdef double[::1] arr = <double[:L]>arrptr

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    free(arrptr)

    # Prevents dead-code elimination
    str(d)

@timefunc("cvarray memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr = cvarray((L,),sizeof(double),'d')

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

输出:

INITIALISATIONS
Running cpython.array buffer
0.100040 0.097140 0.133110 0.121820 0.131630 0.108420 0.112160 μs
Running cpython.array memoryview
0.339480 0.333240 0.378790 0.445720 0.449800 0.414280 0.414060 μs
Running cpython.array raw C type
0.048270 0.049250 0.069770 0.074140 0.076300 0.060980 0.060270 μs
Running numpy.empty_like memoryview
1.006200 1.012160 1.128540 1.212350 1.250270 1.235710 1.241050 μs
Running malloc
0.021850 0.022430 0.037240 0.046260 0.039570 0.043690 0.030720 μs
Running malloc memoryview
1.640200 1.648000 1.681310 1.769610 1.755540 1.804950 1.758150 μs
Running cvarray memoryview
1.332330 1.353910 1.358160 1.481150 1.517690 1.485600 1.490790 μs

ITERATING
Running cpython.array buffer
0.010000 0.027000 0.091000 0.669000 6.314000 64.389000 635.171000 μs
Running cpython.array memoryview
0.013000 0.015000 0.058000 0.354000 3.186000 33.062000 338.300000 μs
Running cpython.array raw C type
0.014000 0.146000 0.979000 9.501000 94.160000 916.073000 9287.079000 μs
Running numpy.empty_like memoryview
0.042000 0.020000 0.057000 0.352000 3.193000 34.474000 333.089000 μs
Running malloc
0.002000 0.004000 0.064000 0.367000 3.599000 32.712000 323.858000 μs
Running malloc memoryview
0.019000 0.032000 0.070000 0.356000 3.194000 32.100000 327.929000 μs
Running cvarray memoryview
0.014000 0.026000 0.063000 0.351000 3.209000 32.013000 327.890000 μs

(“迭代”基准的原因是某些方法在这方面具有令人惊讶的不同特征。)

按初始化速度排序:

malloc
:这是一个严酷的世界,但它很快。如果您需要分配很多东西并拥有不受阻碍的迭代和索引性能,那么这就是它。但通常情况下,你是一个不错的选择...

cpython.array raw C type
:该死的,速度很快。而且很安全。不幸的是,它通过 Python 来访问其数据字段。您可以使用一个绝妙的技巧来避免这种情况:

arr.data.as_doubles[i]

使其达到标准速度,同时消除安全性!这使得它成为malloc

精彩
替代品,基本上是一个漂亮的引用计数版本!

cpython.array buffer
:设置时间仅为
malloc
的三到四倍,这看起来是一个很棒的选择。不幸的是,它的开销很大(尽管与
boundscheck
wraparound
指令相比很小)。这意味着它只能与完全安全的变体竞争,但它是初始化速度最快的变体。您的选择。

cpython.array memoryview

:现在初始化速度比

malloc
慢一个数量级。这很遗憾,但它的迭代速度同样快。这是我建议的标准解决方案,除非
boundscheck
wraparound
已打开(在这种情况下
cpython.array buffer
可能是一个更引人注目的权衡)。

剩下的。唯一有价值的是

numpy

,因为这些对象附加了许多有趣的方法。不过就是这样。

    


9
投票
memoryview

cpython.array
支持目前似乎会导致内存泄漏。这似乎是一个长期存在的问题,因为它在 cython 用户邮件列表
here
2012 年 11 月的一篇文章中提到。使用 Cython 版本 0.22 和 Python 2.7.6 和 Python 2.7.9 运行 Veedrac 的基准测试脚本领先使用 cpython.array
buffer
接口初始化
memoryview
时会导致大量内存泄漏。使用 Python 3.4 运行脚本时不会发生内存泄漏。我已向 Cython 开发人员邮件列表提交了有关此问题的错误报告。
    

© www.soinside.com 2019 - 2024. All rights reserved.