Numpy: ¿Usar un asignador alineado para NumPy?

Creado en 26 nov. 2014  ·  68Comentarios  ·  Fuente: numpy/numpy

Con respecto a la regresión f2py en NumPy 1.9 con fallas en Windows de 32 bits, la pregunta es si NumPy debería comenzar a usar un asignador que ofrezca una alineación garantizada.

https://github.com/scipy/scipy/issues/4168

01 - Enhancement

Comentario más útil

Esta función me resultaría muy útil. Estoy usando un dispositivo FPGA (Altera A10GX) donde el controlador DMA requiere que se usen datos alineados de 64 bytes, esto acelera mi código en 40x (!!!). Sospecho que @nachiket tiene el mismo problema que yo. Escribí algo similar a lo que está usando @eamartin, pero esto es un truco.

Todos 68 comentarios

Aquí hay un ejemplo de un asignador que debería funcionar en todas las plataformas. Se basa descaradamente en esto:

https://sites.google.com/site/ruslancray/lab/bookshelf/interview/ci/low-level/write-an-aligned-malloc-free-function

No hay muchas formas de hacer esto y un código similar está flotando en la red, por lo que extenderlo de esta manera probablemente esté bien. (Y además no implementa realloc.)

Colocar este código en numpy/core/include/numpy/ndarraytypes.h debería garantizar que los ndarrays recién asignados estén correctamente alineados en todas las plataformas.

Este código independiente de la plataforma posiblemente podría reemplazarse con posix_memalign() en POSIX y _aligned_malloc() en Windows. Sin embargo, combinar posix_memalign() con realloc() no es posible, por lo que implementarlo nosotros mismos probablemente sea mejor.

#define NPY_MEMALIGN 32   /* 16 for SSE2, 32 for AVX, 64 for Xeon Phi */ 

static NPY_INLINE
void *PyArray_realloc(void *p, size_t n)
{
    void *p1, **p2, *base;
    size_t old_offs, offs = NPY_MEMALIGN - 1 + sizeof(void*);    
    if (NPY_UNLIKELY(p != NULL)) {
        base = *(((void**)p)-1);
        if (NPY_UNLIKELY((p1 = PyMem_Realloc(base,n+offs)) == NULL)) return NULL;
        if (NPY_LIKELY(p1 == base)) return p;
        p2 = (void**)(((Py_uintptr_t)(p1)+offs) & ~(NPY_MEMALIGN-1));
        old_offs = (size_t)((Py_uintptr_t)p - (Py_uintptr_t)base);
        memmove(p2,(char*)p1+old_offs,n);    
    } else {
        if (NPY_UNLIKELY((p1 = PyMem_Malloc(n + offs)) == NULL)) return NULL;
        p2 = (void**)(((Py_uintptr_t)(p1)+offs) & ~(NPY_MEMALIGN-1));   
    }
    *(p2-1) = p1;
    return (void*)p2;
}    

static NPY_INLINE
void *PyArray_malloc(size_t n)
{
    return PyArray_realloc(NULL, n);
}

static NPY_INLINE
void *PyArray_calloc(size_t n, size_t s)
{
    void *p;
    if (NPY_UNLIKELY((p = PyArray_realloc(NULL,n*s)) == NULL)) return NULL;
    memset(p, 0, n*s);
    return p;
}

static NPY_INLINE        
void PyArray_free(void *p)
{
    void *base = *(((void**)p)-1);
    PyMem_Free(base);
} 

Ya tengo una rama que agrega un asignador alineado, lo excavaré.

Al usar algo como esto, descartamos la opción de usar pythons tracemalloc framework y escasa memoria (no hay alineado_calloc).
@njsmith , ¿estaría dispuesto a interactuar con los desarrolladores de Python nuevamente para agregar otro asignador a su espacio antes de que se publique 3.5? Ya agregaron calloc solo para nosotros, sería una lástima si ahora no pudiéramos usarlo.

Presumiblemente, se podría pasar alineado en los datos de contexto de PyMemAllocatorEx ? Pero NumPy tiene que admitir versiones de Python desde 2.6 y posteriores, por lo que hacer esto en Python 3.5 podría no resolver el problema.

Creo que comprometerse con los desarrolladores de Python en esto antes de la 3.5 es una buena idea,
pero todavía no estoy convencido de que tengamos una buena razón para utilizar una alineación
asignador a corto plazo. No puede ser posible que struct {
double, double} en realidad requiere una alineación mejor que malloc en win32 o
SPARC, porque si eso fuera cierto, nada funcionaría.
El 26 de noviembre de 2014 a las 09:10, "Julian Taylor" [email protected] escribió:

Ya tengo una rama que agrega un asignador alineado, lo excavaré.

Al usar algo como esto, descartamos la opción de usar pitones
marco tracemalloc y memoria escasa (no hay alineado_calloc).
@njsmith https://github.com/njsmith ¿ Estaría dispuesto a participar con
Python devs nuevamente para agregar otro asignador a su ranura antes de que 3.5 sea
¿liberado? Ya agregaron calloc solo para nosotros, sería un desastre si
ahora no podía usarlo.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/numpy/numpy/issues/5312#issuecomment -64534021.

La pregunta con respecto a f2py era qué alineación necesitaría Fortran, no el requisito mínimo de C. La velocidad también es un problema. Tanto la indexación como SIMD funcionan mejor si los datos están alineados correctamente.

Una razón para usar un asignador alineado podría ser la velocidad y garantizar SSE / AVX
La compatibilidad eliminaría el jitter numérico que proviene de tomar diferentes
rutas de código para datos alineados de manera diferente.
.
f2py es más antiguo que el estándar vinculante ISO C en Fortran, y la forma en que
funciona es esencialmente la forma estándar de facto en la interfaz de Fortran
con C, utilizado ampliamente por todos. A la luz de esta experiencia, es
claro que la alineación proporcionada por el sistema malloc es suficiente para la
Compiladores de Fortran que nos importan en la práctica.

Tenga en cuenta que Intel recomienda una alineación de 32 bytes para AVX: https://software.intel.com/en-us/articles/practical-intel-avx-optimization-on-2nd-generation-intel-core-processors

@pitrou Y se recomienda una alineación de 64 bytes para Xeon Phi. Eche un vistazo al comentario detrás de la definición de NPY_MEMALIGN en mi ejemplo de código.

La principal complicación de proporcionar una asignación alineada es que ATM podemos
o engancharse a la infraestructura de tracemalloc xo hacer una asignación alineada,
y arreglar esto requerirá cierta coordinación con CPython upstream (ver

4663).

El 5 de diciembre de 2014 a las 16:44, "Sturla Molden" [email protected] escribió:

@pitrou https://github.com/pitrou Y se recomienda 64 bits para Xeon
Fi. Eche un vistazo al comentario detrás de la definición de NPY_MEMALIGN en
mi ejemplo de código.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/numpy/numpy/issues/5312#issuecomment -65816753.

Entonces, el problema de CPython está en http://bugs.python.org/issue18835.

Dadas las complicaciones con realloc (), puede que no sea realista esperar que CPython resuelva esto en el marco de tiempo 3.5. Numpy quizás debería usar su propio contenedor asignado alineado en su lugar (que debería poder diferir a la API de PyMem y aprovechar tracemalloc, de todos modos).

El código para dicho asignador se incluye arriba. No entiendo el argumento de @juliantaylor , pero probablemente él lo entienda mejor que yo.

Sin embargo, puedo entender lo que quiso decir con calloc. Un calloc no es simplemente un malloc y un memset a cero. Un Memset requerirá que el sistema operativo obtenga las páginas antes de que sean necesarias. AFAIK no hay PyMem_Calloc.

En realidad, CPython 3.5 tiene PyMem_Calloc y amigos.
Creo que @juliantaylor estaba considerando el caso de implementación del uso de funciones del sistema operativo (posix_memalign, etc.). Pero eso no suena necesario.

Por cierto @sturlamolden , su fragmento redefine PyArray_Malloc y amigos, pero la asignación de matrices parece usar PyDataMem_NEW. ¿Estoy malinterpretando algo?

Otro pensamiento es que la asignación alineada puede ser un desperdicio para arreglos pequeños. ¿Quizás debería haber un umbral por debajo del cual se utiliza la asignación estándar?
Además, ¿debería ser configurable la alineación?

Los asignadores se denominan PyArray_malloc y PyArray_free en NumPy 1.9. Mucho ha cambiado en NumPy 1.10.

¿Estás seguro? PyArray_NewFromDescr_int () llama a npy_alloc_cache () y npy_alloc_cache () llama a PyDataMem_NEW ().

Numpy tiene varias interfaces de asignación y no tienen
nombres. PyArray_malloc / free se utilizan para asignaciones "regulares" (por ejemplo, objeto
estructuras). Búferes de datos (ndarray -> punteros de datos, búferes temporales dentro
ufuncs, etc.), sin embargo, se asignan a través de PyDataMem_NEW.

El jueves 15 de enero de 2015 a las 7:48 p. M., Sturla Molden [email protected]
escribió:

Los asignadores se denominan PyArray_malloc y PyArray_free en Numpy 1.9.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/numpy/numpy/issues/5312#issuecomment -70149126.

Nathaniel J. Smith
Investigador postdoctoral - Informática - Universidad de Edimburgo
http://vorpus.org

@njsmith Sí, deberíamos racionalizar las macros de asignación algún día ... Comenzaría con la que se usa para asignar dimensiones para ndarray (IIRC).

Creé PR # 5457 con un parche. Sería bueno recibir comentarios sobre el enfoque.

Hasta donde yo sé, actualmente no hay ningún beneficio de usar un alineado
asignador en numpy?

El viernes 16 de enero de 2015 a las 7:15 p.m., Antoine Pitrou [email protected]
escribió:

Creé PR # 5457 https://github.com/numpy/numpy/pull/5457 con un
parche. Sería bueno recibir comentarios sobre el enfoque.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/numpy/numpy/issues/5312#issuecomment -70305997.

Nathaniel J. Smith
Investigador postdoctoral - Informática - Universidad de Edimburgo
http://vorpus.org

Con Numba determinamos que las instrucciones vectoriales AVX requerían una alineación de 32 bytes para un rendimiento óptimo. Si compila Numpy con AVX habilitado (supongo que requiere opciones específicas del compilador), la alineación también debería marcar la diferencia.

Por curiosidad, ¿tiene alguna medida del mundo real? Pregunto porque hay
Hay tantos factores que influyen en estas cosas (diferente sobrecarga / velocidad
compensaciones en diferentes tamaños de matriz, detalles de los asignadores de memoria, que
también actúo de manera diferente en diferentes tamaños de matriz, etc.) que me resulta difícil
para adivinar si uno termina con un 0.5% de aceleración de un extremo a otro o un 50%
aceleración de extremo a extremo o qué.

El viernes 16 de enero de 2015 a las 8:16 p.m., Antoine Pitrou [email protected]
escribió:

Con Numba determinamos que las instrucciones vectoriales AVX requerían un 32 bytes
alineación para un rendimiento óptimo. Si compila Numpy con AVX habilitado
(requiere opciones específicas del compilador, supongo), la alineación debería hacer una
diferencia también.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/numpy/numpy/issues/5312#issuecomment -70315434.

Nathaniel J. Smith
Investigador postdoctoral - Informática - Universidad de Edimburgo
http://vorpus.org

fwiw en mi i5-4210u no veo una diferencia significativa entre los datos alineados de 16 y 32 bytes en una prueba simple de almacenamiento de carga agregada, el recuento mínimo de ciclos parece menor en un 5% pero la mediana y el percentil 10 son idénticos al 1%

¿Eso es con AVX?

@seibert hizo algunas mediciones AVX con Numba (es decir, generación de código justo a tiempo con LLVM) usando matrices Numpy, creo que intentará ejecutarlas nuevamente para obtener números precisos :-)

Aquí está el punto de referencia con el último maestro de Numba:

http://nbviewer.ipython.org/gist/seibert/6957baddc067140e55fe

Para arreglos float32 (tamaño = 10000) que ejecutan una operación a + b * fabs(a) , vemos una diferencia del 40% en una CPU Intel Core i7-4820K a 3.70GHz (Ivy Bridge).

(Estamos un poco desconcertados de que el autovectorizador LLVM 3.5 no esté generando ningún código de pelado de bucle para corregir los problemas de alineación. No me sorprendería que gcc o clang corrijan esto).

Para otras arquitecturas que planeamos apuntar (como las APU AMD que admiten el estándar HSA), las necesidades de alineación son más estrictas, y los desarrolladores de OpenCL sugieren que tenemos una alineación de 256 bytes para arreglos para un mejor rendimiento.

No sé, el 40% parece una penalización excesiva, veo cero en un i5 haswell (que tiene un rendimiento avx realmente pobre)
¿Podría ser que el compilador jit esté creando dos versiones diferentes del bucle?
¿Tiene un perfil de nivel de montaje de esto (por ejemplo, a través del registro de rendimiento)?

También tal vez el turbo boost se esté activando en el segundo bucle, deshabilitarlo o monitorear el pmu apropiado podría ser interesante

Dado que usamos este sistema para la evaluación comparativa, hemos desactivado TurboBoost en el BIOS.

El JIT solo se ejecuta una vez (una vez que Numba compila una función para un conjunto determinado de tipos de entrada, la almacena en caché), y el JIT se activa antes de la evaluación comparativa por parte de Cell 6 en el cuaderno vinculado.

No he usado el registro de rendimiento antes, pero lo investigaré. ¿Cómo realizó sus evaluaciones comparativas? (De hecho, esperaría que Haswell lo hiciera mejor que Ivy Bridge si la desalineación desencadena algún tipo de uso ineficiente del ancho de banda de caché L2 disponible).

Este documento técnico de Intel ofrece un poco más de información sobre el problema de alineación en la parte inferior de la página 6:

https://software.intel.com/sites/default/files/m/d/4/1/d/8/Practical_Optimization_with_AVX.pdf

El uso de instrucciones Intel AVX en vectores de 32 bytes no alineados significa que cada segunda carga se realizará a través de una división de línea de caché, ya que la línea de caché es de 64 bytes. Esto duplica la tasa de división de la línea de caché en comparación con el código Intel SSE que utiliza vectores de 16 bytes. Es muy probable que una alta tasa de división de la línea de caché en el código con uso intensivo de memoria cause una degradación del rendimiento. Por esa razón, se recomienda encarecidamente alinear los datos a 32 bytes para usarlos con Intel AVX.

Haswell tiene el doble del ancho de banda de caché L2 de Sandy / Ivy Bridge, por lo que es posible que el efecto de las matrices desalineadas no sea significativo en Haswell ...

mi punto de referencia simple es este:
https://gist.github.com/juliantaylor/68e578d140f427ed80bb
Sería interesante ver en ese i7

Resultados en un Core i5-2500K (Sandy Bridge):
4644 6656 7704 10100

Tenga en cuenta que tuve que agregar una fase de calentamiento más larga al comienzo del punto de referencia:

    for (i=0; i<10; i++)
        add(a, 1);

¿Es eso un promedio / mediana / ...? Eso parece bastante significativo, parece que Intel trabajó mucho en él para Haswell.

Es la salida casi estable del punto de referencia después de algunos cientos de ejecuciones en un bucle.

Agregué una implementación SSE2 empaquetada, aquí están las cifras (i5-2500K):
4660 6492 7468 5108 10096
(AVX alineado 32B, AVX 16B (no) alineado, AVX 8B (no) alineado, SSE2 alineado, escalar)

Aquí está la fuente actualizada: https://gist.github.com/pitrou/892219a7d4c6d37de201

Salida casi estable de un Core i5-4200U (CPU Haswell para computadora portátil):
4120 4152 4148 4260 7308

Al menos aquí, AVX desalineado no es peor que SSE2 con una alineación similar.

Aquí está mi MBP (i7-3635QM de cuatro núcleos, 2,4 GHz, Ivy Bridge):
5060 4932 5820 5704 5040

Tuve que cambiar avxintrin.h a immintrin.h y compilar con Intel icc porque gcc 4.8.1 se negó a compilar el código (y también lo hizo clang).

@sturlamolden , ¿es posible que icc esté vectorizando el bucle escalar (el último resultado en los números)?

(tenga en cuenta que el punto de referencia probablemente ejercería más presión sobre el subsistema de caché si la adición involucrara dos matrices de entrada separadas)

No tengo idea.

@sturlamolden Este código fue muy útil para interactuar con algunos SSE2.

Tuve un problema en Windows (todas las versiones de VC, incluida la 2015) no me gusta esta línea

memmove((void*)p2,p1+old_offs,n);    

ya que no admiten aritmética de punteros en void* . Como solución a corto plazo, lo lanzo a char* para hacer los cálculos. Probablemente esto no sea correcto: ¿tiene una mejor idea de lo que haría que se compilara correctamente en Windows?

Culpa mía. La aritmética de punteros en void * es ilegal C. La conversión a char * es correcta.
Se actualizó el ejemplo de código.

¿Cómo obtengo matrices numpy alineadas de 64 bytes? Las direcciones 'ALINEADAS' de https://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.require.html parecen alinearse en una longitud diferente. ¿Existe algún parámetro configurable por el usuario para la longitud de la alineación?

Hice mi propio asignador alineado (Python) que funciona en la memoria (no alineada) proporcionada por Numpy.

import numpy as np

def empty_aligned(n, align):
    """                                                                                                                                     
    Get n bytes of memory wih alignment align.                                                                                              
    """

    a = np.empty(n + (align - 1), dtype=np.uint8)
    data_align = a.ctypes.data % align
    offset = 0 if data_align == 0 else (align - data_align)
    return a[offset : offset + n]


def test(n):
    for i in xrange(1, 1024):
        b = empty_aligned(n, i)

        try:
            assert b.ctypes.data % i == 0
            assert b.size == n
        except AssertionError:
            print i, b.ctypes.data % i, b.size

¿Quizás una solución alternativa de Python como esta es una solución viable?

@eamartin Se trata del código C interno de NumPy y el código de interfaz de Fortran generado por f2py (también código C). Por razones obvias, la implementación de NumPy no puede depender de NumPy.

Sin embargo, puedes usar ese truco para tus proyectos de Python.

@sturlamolden : Sin @nachiket y a otros en una situación similar.

@sturlamolden : No leí lo suficientemente de cerca para darme cuenta de que se trataba de

Sin embargo, ofrecer mejores opciones de alineación en las interfaces de Python sería valioso para desarrollar interfaces de Python entre Numpy y las bibliotecas nativas que tienen requisitos de alineación en los argumentos.

No estoy en contra de ofrecer mejores opciones de alineación en las interfaces de Python 🙂

La solución de "asignador alineado con Python" que sugerí es un truco. Creo que ofrecer alineación en las interfaces de Python sería bueno, pero la forma correcta de hacerlo sería manejar la alineación en el nivel C.

Esta función me resultaría muy útil. Estoy usando un dispositivo FPGA (Altera A10GX) donde el controlador DMA requiere que se usen datos alineados de 64 bytes, esto acelera mi código en 40x (!!!). Sospecho que @nachiket tiene el mismo problema que yo. Escribí algo similar a lo que está usando @eamartin, pero esto es un truco.

Definitivamente recomiendo la alineación de 64 bytes:

  1. ese es el tamaño de la línea de caché
  2. es adecuado para cualquier alineación SIMD hasta AVX512

Aquí estamos casi 5 años después.

¿Alguna idea sobre cómo hacer de esto (la alineación de 64 bytes en particular) una característica estándar?

Este código cython ahora está en NumPy. Por supuesto, esto no cambia el valor predeterminado.

my 2cents: Un asignador alineado ayudaría cuando interactúe con dispositivos de hardware y llamadas a nivel de kernel. Estas interfaces pueden beneficiarse de alinear los búferes con las páginas.

Al fusionar randomgen, ganamos PyArray_realloc_aligned y amigos. ¿Deberíamos mover estas rutinas a numpy/core/include ?

Eso sin duda sería útil, @mattip. ¿Sería posible acceder a esta funcionalidad también desde Python?

Al fusionar randomgen, ganamos PyArray_realloc_aligned y amigos. ¿Deberíamos mover estas rutinas a numpy/core/include ?

Arrancó mi código, ¿verdad? 😂

Probablemente se perdió en algún momento del paso de randomgen a numpy. yo
Creo que tenía constancia de que esto era suyo.

El domingo, 17 de noviembre de 2019, 10:29 Sturla Molden [email protected] escribió:

Parece que no soy un colaborador de loger para el código para el que he escrito
NumPy 🧐:

https://github.com/numpy/numpy/blob/v1.17.2/numpy/_build_utils/src/apple_sgemv_fix.c

https://github.com/numpy/numpy/blob/v1.17.2/numpy/random/src/aligned_malloc/aligned_malloc.h

-
Estás recibiendo esto porque comentaste.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/5312?email_source=notifications&email_token=ABKTSRLWKGXUFE4OK53SJMDQUEMJJA5CNFSM4AYDJQ42YY3PNVWWK3TUL52HS4DFVREXG43VMDVueXHW63SKDFVREXG43VMDVBWIIW63 ,
o darse de baja
https://github.com/notifications/unsubscribe-auth/ABKTSRM7IZIKPGKT4D2W4IDQUEMJJANCNFSM4AYDJQ4Q
.

¿Cuál fue la fuente original del código?

El enlace está muerto, pero se adaptó de un malloc alineado que se veía así:

https://tianrunhe.wordpress.com/2012/04/23/aligned-malloc-in-c/

Era una publicación de github de Sturla. No había ningún archivo de código original.

El domingo 17 de noviembre de 2019 a las 12:04, Matti Picus [email protected] escribió:

¿Cuál fue la fuente original del código?

-
Estás recibiendo esto porque comentaste.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/5312?email_source=notifications&email_token=ABKTSRKKJC4K6C4LW4GFYULQUEXNDA5CNFSM4AYDJQ42YY3PNVWWK3TUL52HS4DFVREXG43WWK3TUL52HS4DFVREXG43KSMVEEX9IKH4DFVREXG43VMDVEEX9IK
o darse de baja
https://github.com/notifications/unsubscribe-auth/ABKTSRIVMQFEJ5EP227PXL3QUEXNDANCNFSM4AYDJQ4Q
.

Esto es para el malloc alineado.

El domingo , 17 de noviembre de 2019, 12:14 Kevin Sheppard
escribió:

Era una publicación de github de Sturla. No había ningún archivo de código original.

El domingo 17 de noviembre de 2019 a las 12:04, Matti Picus [email protected] escribió:

¿Cuál fue la fuente original del código?

-
Estás recibiendo esto porque comentaste.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/5312?email_source=notifications&email_token=ABKTSRKKJC4K6C4LW4GFYULQUEXNDA5CNFSM4AYDJQ42YY3PNVWWK3TUL52HS4DFVREXG43WWK3TUL52HS4DFVREXG43KSMVEEX9IKH4DFVREXG43VMDVEEX9IK
o darse de baja
https://github.com/notifications/unsubscribe-auth/ABKTSRIVMQFEJ5EP227PXL3QUEXNDANCNFSM4AYDJQ4Q
.

¿Todos los que contribuyen con 50 líneas de código a Numpy obtienen un encabezado de derechos de autor dedicado? Podría revisar mis contribuciones y ver si se aplica alguna :-)

Parece que ya no contribuyo con el código que escribí para NumPy 🧐:

Eres y siempre lo serás :)

¿Todos los que contribuyen con 50 líneas de código a Numpy obtienen un encabezado de derechos de autor dedicado? Podría revisar mis contribuciones y ver si se aplica alguna :-)

No Tratamos de evitar codificar tales cosas dentro del código fuente, ya que siempre será tremendamente incompleto y difícil de mantener. Pedimos a las personas que se incluyan en THANKS.txt ; Estoy buscando una alternativa mejor a eso, porque ese archivo a menudo genera conflictos de fusión.

¿Fue útil esta página
0 / 5 - 0 calificaciones