Benchmarking (python vs.c ++ usando BLAS) y (numpy)

107

Me gustaría escribir un programa que haga un uso extensivo de las funcionalidades de álgebra lineal BLAS y LAPACK. Dado que el rendimiento es un problema, hice una evaluación comparativa y me gustaría saber si el enfoque que tomé es legítimo.

Tengo, por así decirlo, tres concursantes y quiero probar su desempeño con una simple multiplicación matriz-matriz. Los concursantes son:

  1. Numpy, haciendo uso únicamente de la funcionalidad de dot.
  2. Python, llamando a las funcionalidades BLAS a través de un objeto compartido.
  3. C ++, llamando a las funcionalidades BLAS a través de un objeto compartido.

Guión

Implementé una multiplicación matriz-matriz para diferentes dimensiones i. iva de 5 a 500 con un incremento de 5 y las matrices m1y m2se configuran así:

m1 = numpy.random.rand(i,i).astype(numpy.float32)
m2 = numpy.random.rand(i,i).astype(numpy.float32)

1. Numpy

El código utilizado se ve así:

tNumpy = timeit.Timer("numpy.dot(m1, m2)", "import numpy; from __main__ import m1, m2")
rNumpy.append((i, tNumpy.repeat(20, 1)))

2. Python, llamando a BLAS a través de un objeto compartido

Con la función

_blaslib = ctypes.cdll.LoadLibrary("libblas.so")
def Mul(m1, m2, i, r):

    no_trans = c_char("n")
    n = c_int(i)
    one = c_float(1.0)
    zero = c_float(0.0)

    _blaslib.sgemm_(byref(no_trans), byref(no_trans), byref(n), byref(n), byref(n), 
            byref(one), m1.ctypes.data_as(ctypes.c_void_p), byref(n), 
            m2.ctypes.data_as(ctypes.c_void_p), byref(n), byref(zero), 
            r.ctypes.data_as(ctypes.c_void_p), byref(n))

el código de prueba se ve así:

r = numpy.zeros((i,i), numpy.float32)
tBlas = timeit.Timer("Mul(m1, m2, i, r)", "import numpy; from __main__ import i, m1, m2, r, Mul")
rBlas.append((i, tBlas.repeat(20, 1)))

3. c ++, llamando a BLAS a través de un objeto compartido

Ahora el código c ++ naturalmente es un poco más largo, así que reduzco la información al mínimo.
Cargo la función con

void* handle = dlopen("libblas.so", RTLD_LAZY);
void* Func = dlsym(handle, "sgemm_");

Mido el tiempo gettimeofdayasí:

gettimeofday(&start, NULL);
f(&no_trans, &no_trans, &dim, &dim, &dim, &one, A, &dim, B, &dim, &zero, Return, &dim);
gettimeofday(&end, NULL);
dTimes[j] = CalcTime(start, end);

donde jhay un bucle que se ejecuta 20 veces. Calculo el tiempo transcurrido con

double CalcTime(timeval start, timeval end)
{
double factor = 1000000;
return (((double)end.tv_sec) * factor + ((double)end.tv_usec) - (((double)start.tv_sec) * factor + ((double)start.tv_usec))) / factor;
}

Resultados

El resultado se muestra en la siguiente gráfica:

ingrese la descripción de la imagen aquí

Preguntas

  1. ¿Crees que mi enfoque es justo o hay algunos gastos generales innecesarios que puedo evitar?
  2. ¿Esperaría que el resultado mostrara una discrepancia tan grande entre el enfoque de c ++ y python? Ambos utilizan objetos compartidos para sus cálculos.
  3. Como prefiero usar Python para mi programa, ¿qué puedo hacer para aumentar el rendimiento al llamar a las rutinas BLAS o LAPACK?

Descargar

El benchmark completo se puede descargar aquí . (JF Sebastian hizo posible ese enlace ^^)

Woltan
fuente
en su enfoque ctypes, tiene la asignación de memoria dentro de la función medida. ¿Su código de C ++ sigue este enfoque? Sin embargo, en comparación con la multiplicación de matrices esto no debe significar una gran diferencia ....
rocksportrocker
@rocksportrocker Tienes razón. La asignación de memoria para la rmatriz es injusta. Estoy resolviendo el "problema" ahora mismo y publico los nuevos resultados.
Woltan
1. asegúrese de que las matrices tengan el mismo diseño de memoria np.ascontiguousarray()(considere el orden C vs. Fortran). 2. asegúrese de que np.dot()utiliza el mismo libblas.so.
jfs
@JFSebastian Ambos arreglos m1y m2tienen el ascontiguousarrayindicador como True. Y numpy usa el mismo objeto compartido que C lo hace. En cuanto al orden de la matriz: actualmente no estoy interesado en el resultado del cálculo, por lo tanto, el orden es irrelevante.
Woltan
1
@Woltan: no uses filefactory, el servicio es horrible. Agregué su punto de referencia a github: woltan-benchmark . Si usa github, podría agregarlo como colaborador.
jfs

Respuestas:

58

He ejecutado su punto de referencia . No hay diferencia entre C ++ y numpy en mi máquina:

punto de referencia de woltan

¿Crees que mi enfoque es justo o hay algunos gastos generales innecesarios que puedo evitar?

Parece justo porque no hay diferencia en los resultados.

¿Esperaría que el resultado mostrara una discrepancia tan grande entre el enfoque de c ++ y python? Ambos utilizan objetos compartidos para sus cálculos.

No.

Como prefiero usar Python para mi programa, ¿qué puedo hacer para aumentar el rendimiento al llamar a las rutinas BLAS o LAPACK?

Asegúrese de que numpy utilice una versión optimizada de las bibliotecas BLAS / LAPACK en su sistema.

jfs
fuente
4
Entonces, ¿qué hizo mal el póster original? Ojalá hubiera comentado esta publicación. ¿Confirma que Numpy es tan rápido como C ++?
wmac
Su código C ++ funciona más lento que los carteles originales. ¿Ha compilado bajo optimización?
cdcdcd
@cdcdcd no es mi código. Haga clic en el enlace y ejecute el punto de referencia usted mismo con diferentes opciones de optimización (consulte el Makefile). Aunque el código no recompila ni blas ni lapack.
jfs
73

ACTUALIZACIÓN (30.07.2014):

Vuelvo a ejecutar el punto de referencia en nuestro nuevo HPC. Tanto el hardware como la pila de software cambiaron de la configuración en la respuesta original.

Puse los resultados en una hoja de cálculo de Google (contiene también los resultados de la respuesta original).

Hardware

Nuestro HPC tiene dos nodos diferentes, uno con CPU Intel Sandy Bridge y otro con las CPU Ivy Bridge más nuevas:

Sandy (MKL, OpenBLAS, ATLAS):

  • CPU : 2 x 16 Intel (R) Xeon (R) E2560 Sandy Bridge a 2,00 GHz (16 núcleos)
  • RAM : 64 GB

Ivy (MKL, OpenBLAS, ATLAS):

  • CPU : 2 x 20 Intel (R) Xeon (R) E2680 V2 Ivy Bridge a 2,80 GHz (20 núcleos, con HT = 40 núcleos)
  • RAM : 256 GB

Software

La pila de software es para ambos nodos, sam. En lugar de GotoBLAS2 , se utiliza OpenBLAS y también hay un ATLAS BLAS de subprocesos múltiples que se establece en 8 subprocesos (codificado).

  • SO : Suse
  • Compilador Intel : ictce-5.3.0
  • Numpy: 1.8.0
  • OpenBLAS: 0.2.6
  • ATLAS : 3.8.4

Benchmark de producto escalar

El código de referencia es el mismo que se muestra a continuación. Sin embargo, para las nuevas máquinas también ejecuté el punto de referencia para los tamaños de matriz 5000 y 8000 .
La siguiente tabla incluye los resultados de referencia de la respuesta original (renombrado: MKL -> Nehalem MKL, Netlib Blas -> Nehalem Netlib BLAS, etc.)

Multiplicación de matrices (tamaños = [1000,2000,3000,5000,8000])

Rendimiento de un solo hilo: rendimiento de un solo hilo

Rendimiento de subprocesos múltiples (8 subprocesos): rendimiento de subprocesos múltiples (8 subprocesos)

Hilos frente al tamaño de la matriz (Ivy Bridge MKL) : Tamaño de matriz frente a subprocesos

Suite Benchmark

suite de referencia

Rendimiento de un solo hilo: ingrese la descripción de la imagen aquí

Rendimiento de subprocesos múltiples (8 subprocesos): ingrese la descripción de la imagen aquí

Conclusión

Los nuevos resultados de referencia son similares a los de la respuesta original. OpenBLAS y MKL funcionan al mismo nivel, con la excepción de la prueba Eigenvalue . Los autovalor realiza la prueba solamente razonablemente bien en OpenBLAS en el modo de un solo subproceso . En el modo de subprocesos múltiples, el rendimiento es peor.

El "gráfico de tamaño de matriz frente a subprocesos" también muestra que aunque MKL y OpenBLAS generalmente escalan bien con el número de núcleos / subprocesos, depende del tamaño de la matriz. Para matrices pequeñas, agregar más núcleos no mejorará mucho el rendimiento.

También hay un aumento de rendimiento de aproximadamente un 30% desde Sandy Bridge hasta Ivy Bridge, lo que podría deberse a una mayor frecuencia de reloj (+ 0,8 Ghz) y / o una mejor arquitectura.


Respuesta original (04.10.2011):

Hace algún tiempo tuve que optimizar algunos cálculos / algoritmos de álgebra lineal que se escribieron en Python usando numpy y BLAS, así que comparé / probé diferentes configuraciones de numpy / BLAS.

Específicamente probé:

  • Numpy con ATLAS
  • Numpy con GotoBlas2 (1.13)
  • Numpy con MKL (11.1 / 073)
  • Numpy con Accelerate Framework (Mac OS X)

Ejecuté dos puntos de referencia diferentes:

  1. producto escalar simple de matrices con diferentes tamaños
  2. Paquete de referencia que se puede encontrar aquí .

Aquí están mis resultados:

Máquinas

Linux (MKL, ATLAS, No-MKL, GotoBlas2):

  • Sistema operativo : Ubuntu Lucid 10.4 de 64 bits.
  • CPU : 2 x 4 Intel (R) Xeon (R) E5504 a 2,00 GHz (8 núcleos)
  • RAM : 24 GB
  • Compilador Intel : 11.1 / 073
  • Scipy : 0.8
  • Numpy : 1.5

Mac Book Pro (Accelerate Framework):

  • Sistema operativo : Mac OS X Snow Leopard (10.6)
  • CPU : 1 Intel Core 2 Duo 2,93 Ghz (2 núcleos)
  • RAM : 4 GB
  • Scipy : 0,7
  • Numpy : 1.3

Servidor Mac (Accelerate Framework):

  • SO : Mac OS X Snow Leopard Server (10.6)
  • CPU : 4 X Intel (R) Xeon (R) E5520 a 2,26 Ghz (8 núcleos)
  • RAM : 4 GB
  • Scipy : 0.8
  • Numpy : 1.5.1

Punto de referencia de producto escalar

Codigo :

import numpy as np
a = np.random.random_sample((size,size))
b = np.random.random_sample((size,size))
%timeit np.dot(a,b)

Resultados :

    Sistema | tamaño = 1000 | tamaño = 2000 | tamaño = 3000 |
netlib BLAS | 1350 ms | 10900 ms | 39200 ms |    
ATLAS (1 CPU) | 314 ms | 2560 ms | 8700 ms |     
MKL (1 CPU) | 268 ms | 2110 ms | 7120 ms |
MKL (2 CPU) | - | - | 3660 ms |
MKL (8 CPU) | 39 ms | 319 ms | 1000 ms |
GotoBlas2 (1 CPU) | 266 ms | 2100 ms | 7280 ms |
GotoBlas2 (2 CPU) | 139 ms | 1009 ms | 3690 ms |
GotoBlas2 (8 CPU) | 54 ms | 389 ms | 1250 ms |
Mac OS X (1 CPU) | 143 ms | 1060 ms | 3605 ms |
Servidor Mac (1 CPU) | 92 ms | 714 ms | 2130 ms |

Benchmark de producto escalar - gráfico

Suite Benchmark

Código :
para obtener información adicional sobre la suite de referencia, consulte aquí .

Resultados :

    Sistema | valores propios | svd | det | inv | punto |
netlib BLAS | 1688 ms | 13102 ms | 438 ms | 2155 ms | 3522 ms |
ATLAS (1 CPU) | 1210 ms | 5897 ms | 170 ms | 560 ms | 893 ms |
MKL (1 CPU) | 691 ms | 4475 ms | 141 ms | 450 ms | 736 ms |
MKL (2 CPU) | 552 ms | 2718 ms | 96 ms | 267 ms | 423 ms |
MKL (8 CPU) | 525 ms | 1679 ms | 60 ms | 137 ms | 197 ms |  
GotoBlas2 (1 CPU) | 2124 ms | 4636 ms | 147 ms | 456 ms | 743 ms |
GotoBlas2 (2 CPU) | 1560 ms | 3278 ms | 116 ms | 295 ms | 460 ms |
GotoBlas2 (8 CPU) | 741 ms | 2914 ms | 82 ms | 262 ms | 192 ms |
Mac OS X (1 CPU) | 948 ms | 4339 ms | 151 ms | 318 ms | 566 ms |
Servidor Mac (1 CPU) | 1033 ms | 3645 ms | 99 ms | 232 ms | 342 ms |

Paquete de referencia - gráfico

Instalación

La instalación de MKL incluyó la instalación completa de Intel Compiler Suite, que es bastante sencilla. Sin embargo, debido a algunos errores / problemas, configurar y compilar numpy con soporte MKL fue un poco complicado.

GotoBlas2 es un pequeño paquete que se puede compilar fácilmente como una biblioteca compartida. Sin embargo, debido a un error , debe volver a crear la biblioteca compartida después de construirla para usarla con numpy.
Además de esta construcción, para múltiples plataformas de destino no funcionó por alguna razón. Así que tuve que crear un archivo .so para cada plataforma para la que quiero tener un archivo libgoto2.so optimizado .

Si instala numpy desde el repositorio de Ubuntu, se instalará y configurará automáticamente numpy para usar ATLAS . La instalación de ATLAS desde la fuente puede llevar algún tiempo y requiere algunos pasos adicionales (fortran, etc.).

Si instala numpy en una máquina Mac OS X con puertos Fink o Mac , configurará numpy para usar ATLAS o Accelerate Framework de Apple . Puede verificar ejecutando ldd en el archivo numpy.core._dotblas o llamando a numpy.show_config () .

Conclusiones

MKL funciona mejor seguido de cerca por GotoBlas2 .
En la prueba de valor propio , GotoBlas2 se comporta sorprendentemente peor de lo esperado. No estoy seguro de por qué es así.
Accelerate Framework de Apple funciona realmente bien, especialmente en el modo de un solo subproceso (en comparación con las otras implementaciones de BLAS).

Tanto GotoBlas2 como MKL escalan muy bien con el número de subprocesos. Entonces, si tiene que lidiar con matrices grandes, ejecutarlo en varios subprocesos será de gran ayuda.

En cualquier caso, no use la implementación predeterminada de netlib blas porque es demasiado lenta para cualquier trabajo computacional serio.

En nuestro clúster también instalé ACML de AMD y el rendimiento fue similar al de MKL y GotoBlas2 . No tengo números difíciles.

Personalmente, recomendaría usar GotoBlas2 porque es más fácil de instalar y es gratis.

Si desea codificar en C ++ / C, también consulte Eigen3, que se supone que supera a MKL / GotoBlas2 en algunos casos y también es bastante fácil de usar.

Ümit
fuente
¡Muchas gracias por esta respuesta tan elaborada!
Woltan
Muy completo, gracias! Me pregunto, tres años después, si OpenBLAS (que yo sepa, es un descendiente de GotoBLAS) funcionaría mejor. He leído en alguna parte que supera a MKL, pero no puedo encontrar la fuente en este momento.
¡Gracias! Esta es mi impresión de 0 (me preguntaba si esto era solo mi instalación): OpenBLAS no funciona tan bien en el modo multiproceso cuando se trata de diagonalizar matrices (diagonalizo en scipy, que está vinculado a OpenBLAS).
@William: por lo general, no es necesario vincular específicamente scipy a openblas porque usará la configuración numpy durante la instalación y, en realidad, la mayoría de las llamadas BLAS / Lapack se reenviarán a numpy de todos modos. Entonces, si numpy está correctamente vinculado contra openblas, todo debería funcionar bien.
Ümit
@ Ümit: ¡Gracias! Estoy tratando de configurar numpy para vincularlo con MKL ahora.
20

Aquí hay otro punto de referencia (en Linux, simplemente escriba make): http://dl.dropbox.com/u/5453551/blas_call_benchmark.zip

http://dl.dropbox.com/u/5453551/blas_call_benchmark.png

No veo esencialmente ninguna diferencia entre los diferentes métodos para matrices grandes, entre Numpy, Ctypes y Fortran. (Fortran en lugar de C ++ --- y si esto importa, su punto de referencia probablemente esté roto).

Su CalcTimefunción en C ++ parece tener un error de signo. ... + ((double)start.tv_usec))debería ser en su lugar ... - ((double)start.tv_usec)). ¿Quizás su punto de referencia también tiene otros errores, por ejemplo, comparar entre diferentes bibliotecas BLAS o diferentes configuraciones BLAS como el número de subprocesos, o entre el tiempo real y el tiempo de la CPU?

EDITAR : no se pudieron contar las llaves en la CalcTimefunción, está bien.

Como pauta: si realiza una evaluación comparativa, publique siempre todo el código en algún lugar. Comentar los puntos de referencia, especialmente cuando sorprende, sin tener el código completo, no suele ser productivo.


Para saber contra qué BLAS Numpy está vinculado, haga lo siguiente:

$ python
Python 2.7.2+ (predeterminado, 16 de agosto de 2011, 07:24:41) 
[GCC 4.6.1] en linux2
Escriba "ayuda", "derechos de autor", "créditos" o "licencia" para obtener más información.
>>> importar numpy.core._dotblas
>>> numpy.core._dotblas .__ archivo__
'/usr/lib/pymodules/python2.7/numpy/core/_dotblas.so'
>>> 
$ ldd /usr/lib/pymodules/python2.7/numpy/core/_dotblas.so
    linux-vdso.so.1 => (0x00007fff5ebff000)
    libblas.so.3gf => /usr/lib/libblas.so.3gf (0x00007fbe618b3000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbe61514000)

ACTUALIZACIÓN : si no puede importar numpy.core._dotblas, su Numpy está usando su copia de respaldo interna de BLAS, que es más lenta y no está diseñada para usarse en computación de rendimiento. La respuesta de @Woltan a continuación indica que esta es la explicación de la diferencia que ve en Numpy vs.Ctypes + BLAS.

Para solucionar la situación, necesita ATLAS o MKL --- consulte estas instrucciones: http://scipy.org/Installing_SciPy/Linux La mayoría de las distribuciones de Linux se envían con ATLAS, por lo que la mejor opción es instalar su libatlas-devpaquete (el nombre puede variar) .

pv.
fuente
He ejecutado su punto de referencia; los resultados son los mismos
jfs
Muchas gracias por tu publicación. Ejecuté su punto de referencia con este resultado. Entonces no puedo reproducir el tuyo. Para comprobar qué BLAS está usando mi numpy: No puedo import numpy.core._dotblas. Cual podría ser el problema aquí? Intentaré limpiar mi punto de referencia y escribir un archivo MAKE para que otros lo prueben.
Woltan
2
@Woltan: el hecho de que no pueda importar numpy.core._dotblas significa que su Numpy está usando su copia de respaldo interna de BLAS (¡ más lenta y no está diseñada para usarse en computación de rendimiento!), En lugar de la biblioteca BLAS que tiene en su sistema. Esto explica los resultados que obtuvo del punto de referencia. Para solucionar la situación, necesita instalar una versión BLAS con la que Numpy pueda trabajar --- lo que significa ATLAS o MKL. Aquí hay un conjunto de instrucciones: scipy.org/Installing_SciPy/Linux
pv.
@pv .: ¿Podría ejecutar el punto de referencia de Woltan para comparar resultados?
jfs
1
En Mac, puede usar en otool -Llugar de ldden Linux
RichVel
9

Dado el rigor que ha demostrado con su análisis, me sorprenden los resultados hasta ahora. Pongo esto como una 'respuesta' pero solo porque es demasiado largo para un comentario y brinda una posibilidad (aunque espero que lo hayas considerado).

Hubiera pensado que el enfoque numpy / python no agregaría mucha sobrecarga para una matriz de complejidad razonable, ya que a medida que aumenta la complejidad, la proporción en la que participa Python debería ser pequeña. Estoy más interesado en los resultados en el lado derecho del gráfico, pero una discrepancia de órdenes de magnitud mostrada allí sería inquietante.

Me pregunto si está utilizando los mejores algoritmos que numpy puede aprovechar. De la guía de compilación para linux:

"Construir FFTW (3.1.2): Versiones SciPy> = 0.7 y Numpy> = 1.2: Debido a problemas de licencia, configuración y mantenimiento, se eliminó el soporte para FFTW en las versiones de SciPy> = 0.7 y NumPy> = 1.2. En su lugar, ahora se usa una versión incorporada de fftpack. Hay un par de formas de aprovechar la velocidad de FFTW si es necesario para su análisis. Cambie a una versión Numpy / Scipy que incluya soporte. Instale o cree su propio contenedor de FFTW. Consulte http: //developer.berlios.de/projects/pyfftw/ como un ejemplo no respaldado ".

¿Compilaste numpy con mkl? ( http://software.intel.com/en-us/articles/intel-mkl/ ). Si está ejecutando en linux, las instrucciones para compilar numpy con mkl están aquí: http://www.scipy.org/Installing_SciPy/Linux#head-7ce43956a69ec51c6f2cedd894a4715d5bfff974 (a pesar de la URL). La parte clave es:

[mkl]
library_dirs = /opt/intel/composer_xe_2011_sp1.6.233/mkl/lib/intel64
include_dirs = /opt/intel/composer_xe_2011_sp1.6.233/mkl/include
mkl_libs = mkl_intel_lp64,mkl_intel_thread,mkl_core 

Si está en Windows, puede obtener un binario compilado con mkl, (y también obtener pyfftw y muchos otros algoritmos relacionados) en: http://www.lfd.uci.edu/~gohlke/pythonlibs/ , con un deuda de gratitud con Christoph Gohlke en el Laboratorio de Dinámica de Fluorescencia, UC Irvine.

Advertencia, en cualquier caso, hay muchos problemas de licencia y demás que debe tener en cuenta, pero la página de inteligencia los explica. Nuevamente, imagino que ha considerado esto, pero si cumple con los requisitos de licencia (que en Linux es muy fácil de hacer), esto aceleraría mucho la parte numpy en relación con el uso de una compilación automática simple, sin siquiera FFTW. Me interesará seguir este hilo y ver qué piensan los demás. Independientemente, excelente rigor y excelente pregunta. Gracias por publicarlo.

Profano
fuente
Gracias por su elaborado "comentario" ^^. Para aclarar mi configuración de python / numpy / BLAS: Seguí esta guía de instalación. Estoy en un sistema operativo Linux y las versiones son: Python 2.7, Scipy 0.9 Numpy 1.6. Desafortunadamente, no construí FFTW de antemano, ni usé mkl ...
Woltan
En cierto modo, es una suerte. Significa que hay un gran margen de mejora en los resultados de Python y parece que le gustaría usar Python. Creo que si modificas tu compilación a la que se muestra en el enlace, estarás mucho más contento con la velocidad de Numpy, aunque todavía me fascinaría ver cómo se compara con tu implementación de C ++.
Profano
También podrías intentar construir ATLAS, pero eso sonaba como muchos dolores de cabeza para mis necesidades de desempeño, así que no tengo experiencia. Me imagino que si está interesado en usar Python pero puede usar C ++, habrá algún punto en el que el costo de configuración de hacer mucha compilación especial supere el ahorro de lenguaje, y sería más fácil hacer C ++. Pero mkl y fftw deberían ser bastante sencillos.
Profano
1
Actualmente MKL, Accelerate y OpenBLAS tienen un rendimiento similar. Sin embargo, OpenBLAS es más escalable que MKL.
Sturla Molden