Me gustaría escribir un programa que haga un uso extensivo de las funcionalidades de álgebra lineal de BLAS y LAPACK. Dado que el rendimiento es un problema, hice algunas evaluaciones comparativas y me gustaría saber si el enfoque que tomé es legítimo.
Tengo, por así decirlo, tres concursantes y quiero probar su rendimiento con una matriz-matriz simple multiplicación. Los concursantes son:
- Numpy, haciendo uso únicamente de la funcionalidad de
dot
. - Python, llamando a las funcionalidades de BLAS a través de un objeto compartido.
- C++, llamando a las funcionalidades de BLAS a través de un objeto compartido.
Escenario
Implementé una multiplicación matriz-matriz para diferentes dimensiones yo.
i
va de 5 a 500 con un incremento de 5 y las matrices m1
y m2
se 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) uno = c_float(1.0) cero = c_float(0.0) _blaslib.sgemm_(byref(no_trans), byref(no_trans), byref(n), byref (n), byref(n), byref(uno), m1.ctypes.data_as(ctypes.c_void_p), byref(n), m2.ctypes.data_as(ctypes.c_void_p), byref(n), byref(cero) , 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_");
Yo mido el tiempo con gettimeofday
así:
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 j
es un bucle que se ejecuta 20 veces. Calculo el tiempo transcurrido con
doble CalcTime(timeval start, timeval end) { double factor = 1000000; return (((doble)fin.tv_sec) * factor + ((doble)fin.tv_usec) - (((doble)inicio.tv_sec) * factor + ((doble)inicio.tv_usec))) / factor; }
Resultados
El resultado se muestra en el siguiente gráfico:
Preguntas
- ¿Cree que mi enfoque es justo o hay algunos gastos generales innecesarios que puedo evitar?
- ¿Esperaría que el resultado mostrara una discrepancia tan grande entre el enfoque de C++ y Python? Ambos usan objetos compartidos para sus cálculos.
- Dado que preferiría usar python para mi programa, ¿qué podría 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^^)