Eu gostaria de escrever um programa que faz uso extensivo das funcionalidades da álgebra linear BLAS e LAPACK. Como o desempenho é um problema, fiz alguns benchmarking e gostaria de saber se a abordagem que usei é legítima.
Tenho, por assim dizer, três concorrentes e quero testar seu desempenho com uma simples multiplicação matriz-matriz. Os competidores são:
- Numpy, utilizando apenas a funcionalidade do
dot
. - Python, chamando as funcionalidades do BLAS através de um objeto compartilhado.
- C ++, chamando as funcionalidades do BLAS através de um objeto compartilhado.
Cenário
Implementei uma multiplicação matriz-matriz para diferentes dimensões i
. i
vai de 5 a 500 com um incremento de 5 e as matrizes m1
e m2
são configuradas assim:
m1 = numpy.random.rand(i,i).astype(numpy.float32)
m2 = numpy.random.rand(i,i).astype(numpy.float32)
1. Numpy
O código usado é parecido com este:
tNumpy = timeit.Timer("numpy.dot(m1, m2)", "import numpy; from __main__ import m1, m2")
rNumpy.append((i, tNumpy.repeat(20, 1)))
2. Python, chamando BLAS por meio de um objeto compartilhado
Com a função
_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))
o código de teste é semelhante a este:
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 ++, chamando BLAS por meio de um objeto compartilhado
Agora o código c ++ naturalmente é um pouco mais longo, então reduzo as informações ao mínimo.
Eu carrego a função com
void* handle = dlopen("libblas.so", RTLD_LAZY);
void* Func = dlsym(handle, "sgemm_");
Eu medi o tempo gettimeofday
assim:
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);
onde j
está um loop rodando 20 vezes. Eu calculo o tempo que passou com
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
O resultado é mostrado no gráfico abaixo:
Questões
- Você acha que minha abordagem é justa ou há alguma sobrecarga desnecessária que posso evitar?
- Você esperaria que o resultado mostrasse uma discrepância tão grande entre a abordagem c ++ e python? Ambos estão usando objetos compartilhados para seus cálculos.
- Como prefiro usar python para meu programa, o que posso fazer para aumentar o desempenho ao chamar rotinas BLAS ou LAPACK?
Baixar
O benchmark completo pode ser baixado aqui . (JF Sebastian tornou esse link possível ^^)
r
matriz é injusta. Estou resolvendo o "problema" agora mesmo e posto os novos resultados.
np.ascontiguousarray()
(considere a ordem C vs. Fortran). 2. certifique-se de que np.dot()
usa o mesmo libblas.so
.
m1
e m2
têm o ascontiguousarray
sinalizador como True
. E numpy usa o mesmo objeto compartilhado que C faz. Quanto à ordem da matriz: Atualmente, não estou interessado no resultado do cálculo, portanto, a ordem é irrelevante.