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:
- Numpy, haciendo uso únicamente de la funcionalidad de
dot
. - Python, llamando a las funcionalidades BLAS a través de un objeto compartido.
- 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
. 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)
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 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
hay 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:
Preguntas
- ¿Crees 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 utilizan objetos compartidos para sus cálculos.
- 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 ^^)
r
matriz es injusta. Estoy resolviendo el "problema" ahora mismo y publico los nuevos resultados.
np.ascontiguousarray()
(considere el orden C vs. Fortran). 2. asegúrese de que np.dot()
utiliza el mismo libblas.so
.
m1
y m2
tienen el ascontiguousarray
indicador 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.