84 votes

Quelle est la méthode recommandée pour allouer de la mémoire à une vue mémoire typée ?

Les Documentation Cython sur les vues mémoire typées énumérer trois façons d'affecter à une vue de mémoire typée :

  1. à partir d'un pointeur C brut,
  2. d'un np.ndarray et
  3. d'un cython.view.array .

Supposons que je ne reçoive pas de données de l'extérieur dans ma fonction Cython, mais que je veuille allouer de la mémoire et la renvoyer sous la forme d'un fichier np.ndarray Quelle option dois-je choisir ? Je suppose également que la taille de ce tampon n'est pas une constante de compilation, c'est-à-dire que je ne peux pas l'allouer sur la pile, mais que j'aurais besoin de l'allouer sur la pile. malloc pour l'option 1.

Les trois options se présentent donc comme suit :

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

Ce qui me surprend, c'est que dans les trois cas, Cython génère beaucoup de code pour l'allocation de la mémoire, en particulier un appel à __Pyx_PyObject_to_MemoryviewSlice_dc_int . Cela suggère (et je peux me tromper, ma compréhension du fonctionnement interne de Cython est très limitée) qu'il crée d'abord un objet Python, puis le "coule" dans une vue mémoire, ce qui semble être une surcharge inutile.

A repère simple ne révèle pas de grande différence entre les trois méthodes, la 2. étant la plus rapide de peu.

Laquelle des trois méthodes est recommandée ? Ou existe-t-il une autre option, meilleure ?

Question complémentaire : Je souhaite enfin renvoyer le résultat sous la forme d'un np.ndarray après avoir travaillé avec cette vue de la mémoire dans la fonction. Est-ce qu'une vue mémoire typée est le meilleur choix ou est-ce que je préfère utiliser l'ancienne interface tampon comme ci-dessous pour créer une vue mémoire de type ndarray en premier lieu ?

cdef np.ndarray[DTYPE_t, ndim=1] b = np.empty(N, dtype=np.int32)

89voto

Veedrac Points 11712

Regarder aquí pour obtenir une réponse.

L'idée de base est que vous voulez cpython.array.array et cpython.array.clone ( no 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)

EDITAR

Il s'avère que les benchmarks présentés dans ce fil de discussion n'étaient pas fiables. Voici mon jeu, avec mes timings :

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

Sortie :

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

(La raison de la référence aux "itérations" est que certaines méthodes ont des caractéristiques étonnamment différentes à cet égard).

Par ordre de vitesse d'initialisation :

malloc : Le monde est dur, mais il est rapide. Si vous avez besoin d'allouer beaucoup de choses et d'avoir des performances d'itération et d'indexation sans entrave, c'est ce qu'il vous faut. Mais normalement, vous êtes un bon...

cpython.array raw C type : Bon sang, c'est rapide. Et c'est sûr. Malheureusement, il faut passer par Python pour accéder à ses champs de données. Vous pouvez éviter cela en utilisant une astuce merveilleuse :

arr.data.as_doubles[i]

ce qui lui permet d'atteindre la vitesse standard tout en supprimant la sécurité ! Cela en fait un merveilleux remplacement pour malloc La version de la Commission européenne, qui est en fait une version très référencée, est une version de la Commission européenne qui est en fait une version très référencée.

cpython.array buffer : Avec un temps de préparation trois à quatre fois plus court que celui de l malloc Il s'agit d'un pari formidable. Malheureusement, il entraîne des frais généraux importants (bien que minimes par rapport à ceux de la boundscheck et wraparound ). Cela signifie qu'il n'est réellement en concurrence qu'avec les variantes de sécurité totale, mais il est en concurrence avec les variantes de sécurité totale. est le plus rapide à initialiser. Vous avez le choix.

cpython.array memoryview : La vitesse est désormais inférieure d'un ordre de grandeur à celle de l malloc pour l'initialiser. C'est dommage, mais l'itération est tout aussi rapide. C'est la solution standard que je suggère à moins que boundscheck o wraparound sont activés (dans ce cas cpython.array buffer pourrait être un compromis plus convaincant).

Le reste. Le seul qui vaille est numpy en raison des nombreuses méthodes amusantes associées aux objets. Mais c'est tout.

9voto

Matt Graham Points 191

Pour faire suite à la réponse de Veedrac, il faut savoir qu'en utilisant la fonction memoryview le soutien de cpython.array avec python 2.7 semble actuellement conduire à des fuites de mémoire. Ce problème semble exister depuis longtemps puisqu'il est mentionné sur la liste de diffusion cython-users aquí dans un billet datant de novembre 2012. L'exécution du script de benchmark de Veedrac avec Cython version 0.22 avec Python 2.7.6 et Python 2.7.9 conduit à une fuite de mémoire importante lors de l'initialisation d'un fichier de type cpython.array en utilisant soit un buffer o memoryview l'interface. Aucune fuite de mémoire ne se produit lors de l'exécution du script avec Python 3.4. J'ai déposé un rapport de bogue à ce sujet sur la liste de diffusion des développeurs Cython.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X