Comencé una pregunta similar en another thread, pero luego me enfocaba en cómo usar OpenCV. Al no haber logrado lo que originalmente quería, preguntaré aquí exactamente lo que quiero.La forma más rápida de calcular la distancia euclidiana mínima entre dos matrices que contienen vectores de alta dimensión
Tengo dos matrices. Matrix a es 2782x128 y Matrix b es 4000x128, ambos valores de char sin signo. Los valores se almacenan en una única matriz. Para cada vector en a, necesito el índice del vector en b con la distancia euclidiana más cercana.
Ok, ahora mi código para lograr esto:
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <cstdio>
#include <math.h>
#include <time.h>
#include <sys/timeb.h>
#include <iostream>
#include <fstream>
#include "main.h"
using namespace std;
void main(int argc, char* argv[])
{
int a_size;
unsigned char* a = NULL;
read_matrix(&a, a_size,"matrixa");
int b_size;
unsigned char* b = NULL;
read_matrix(&b, b_size,"matrixb");
LARGE_INTEGER liStart;
LARGE_INTEGER liEnd;
LARGE_INTEGER liPerfFreq;
QueryPerformanceFrequency(&liPerfFreq);
QueryPerformanceCounter(&liStart);
int* indexes = NULL;
min_distance_loop(&indexes, b, b_size, a, a_size);
QueryPerformanceCounter(&liEnd);
cout << "loop time: " << (liEnd.QuadPart - liStart.QuadPart)/long double(liPerfFreq.QuadPart) << "s." << endl;
if (a)
delete[]a;
if (b)
delete[]b;
if (indexes)
delete[]indexes;
return;
}
void read_matrix(unsigned char** matrix, int& matrix_size, char* matrixPath)
{
ofstream myfile;
float f;
FILE * pFile;
pFile = fopen (matrixPath,"r");
fscanf (pFile, "%d", &matrix_size);
*matrix = new unsigned char[matrix_size*128];
for (int i=0; i<matrix_size*128; ++i)
{
unsigned int matPtr;
fscanf (pFile, "%u", &matPtr);
matrix[i]=(unsigned char)matPtr;
}
fclose (pFile);
}
void min_distance_loop(int** indexes, unsigned char* b, int b_size, unsigned char* a, int a_size)
{
const int descrSize = 128;
*indexes = (int*)malloc(a_size*sizeof(int));
int dataIndex=0;
int vocIndex=0;
int min_distance;
int distance;
int multiply;
unsigned char* dataPtr;
unsigned char* vocPtr;
for (int i=0; i<a_size; ++i)
{
min_distance = LONG_MAX;
for (int j=0; j<b_size; ++j)
{
distance=0;
dataPtr = &a[dataIndex];
vocPtr = &b[vocIndex];
for (int k=0; k<descrSize; ++k)
{
multiply = *dataPtr++-*vocPtr++;
distance += multiply*multiply;
// If the distance is greater than the previously calculated, exit
if (distance>min_distance)
break;
}
// if distance smaller
if (distance<min_distance)
{
min_distance = distance;
(*indexes)[i] = j;
}
vocIndex+=descrSize;
}
dataIndex+=descrSize;
vocIndex=0;
}
}
Y adjuntan los archivos con matrices de muestras.
estoy usando windows.h sólo para calcular el tiempo, por lo que si desea probar el código en otra plataforma que no sea Windows, basta con cambiar windows.h cabecera y cambiar la forma de calcular el tiempo de consumo.
Este código en mi computadora es de aproximadamente 0.5 segundos. El problema es que tengo otro código en Matlab que hace lo mismo en 0.05 segundos. En mis experimentos, recibo varias matrices como la matriz a por segundo, por lo que 0.5 segundos es demasiado.
Ahora el código MATLAB para calcular esto:
aa=sum(a.*a,2); bb=sum(b.*b,2); ab=a*b';
d = sqrt(abs(repmat(aa,[1 size(bb,1)]) + repmat(bb',[size(aa,1) 1]) - 2*ab));
[minz index]=min(d,[],2);
Ok. El código de Matlab está usando eso (x-a)^2 = x^2 + a^2 - 2ab.
Así que mi próximo intento fue hacer lo mismo. Borré mi propio código para hacer los mismos cálculos, pero fue 1.2 segundos aprox.
Luego, traté de usar diferentes bibliotecas externas. El primer intento fue Eigen:
const int descrSize = 128;
MatrixXi a(a_size, descrSize);
MatrixXi b(b_size, descrSize);
MatrixXi ab(a_size, b_size);
unsigned char* dataPtr = matrixa;
for (int i=0; i<nframes; ++i)
{
for (int j=0; j<descrSize; ++j)
{
a(i,j)=(int)*dataPtr++;
}
}
unsigned char* vocPtr = matrixb;
for (int i=0; i<vocabulary_size; ++i)
{
for (int j=0; j<descrSize; ++j)
{
b(i,j)=(int)*vocPtr ++;
}
}
ab = a*b.transpose();
a.cwiseProduct(a);
b.cwiseProduct(b);
MatrixXi aa = a.rowwise().sum();
MatrixXi bb = b.rowwise().sum();
MatrixXi d = (aa.replicate(1,vocabulary_size) + bb.transpose().replicate(nframes,1) - 2*ab).cwiseAbs2();
int* index = NULL;
index = (int*)malloc(nframes*sizeof(int));
for (int i=0; i<nframes; ++i)
{
d.row(i).minCoeff(&index[i]);
}
cuesta Este código Eigen 1,2 aprox sólo por la línea que dice: ab = a * b.transpose();
Se usó también un código similar usando opencv, y el costo de ab = a * b.transpose(); fue 0.65 segundos.
Por lo tanto, es realmente molesto que matlab pueda hacer lo mismo tan rápido y no puedo hacerlo en C++. Por supuesto, ser capaz de ejecutar mi experimento sería genial, pero creo que la falta de conocimiento es lo que realmente me molesta. ¿Cómo puedo lograr al menos el mismo rendimiento que en Matlab? Cualquier tipo de solución es bienvenida. Quiero decir, cualquier biblioteca externa (gratis si es posible), bucle de desenrollar cosas, elementos de plantilla, instrucciones SSE (sé que existen), cosas de caché. Como dije, mi principal objetivo es aumentar mi conocimiento para poder codificar este tipo de pensamientos con un rendimiento más rápido.
Gracias de antemano
EDIT: más código sugeridos por David Hammen. Fundí las matrices en int antes de hacer ningún cálculo. Aquí está el código:
void min_distance_loop(int** indexes, unsigned char* b, int b_size, unsigned char* a, int a_size)
{
const int descrSize = 128;
int* a_int;
int* b_int;
LARGE_INTEGER liStart;
LARGE_INTEGER liEnd;
LARGE_INTEGER liPerfFreq;
QueryPerformanceFrequency(&liPerfFreq);
QueryPerformanceCounter(&liStart);
a_int = (int*)malloc(a_size*descrSize*sizeof(int));
b_int = (int*)malloc(b_size*descrSize*sizeof(int));
for(int i=0; i<descrSize*a_size; ++i)
a_int[i]=(int)a[i];
for(int i=0; i<descrSize*b_size; ++i)
b_int[i]=(int)b[i];
QueryPerformanceCounter(&liEnd);
cout << "Casting time: " << (liEnd.QuadPart - liStart.QuadPart)/long double(liPerfFreq.QuadPart) << "s." << endl;
*indexes = (int*)malloc(a_size*sizeof(int));
int dataIndex=0;
int vocIndex=0;
int min_distance;
int distance;
int multiply;
/*unsigned char* dataPtr;
unsigned char* vocPtr;*/
int* dataPtr;
int* vocPtr;
for (int i=0; i<a_size; ++i)
{
min_distance = LONG_MAX;
for (int j=0; j<b_size; ++j)
{
distance=0;
dataPtr = &a_int[dataIndex];
vocPtr = &b_int[vocIndex];
for (int k=0; k<descrSize; ++k)
{
multiply = *dataPtr++-*vocPtr++;
distance += multiply*multiply;
// If the distance is greater than the previously calculated, exit
if (distance>min_distance)
break;
}
// if distance smaller
if (distance<min_distance)
{
min_distance = distance;
(*indexes)[i] = j;
}
vocIndex+=descrSize;
}
dataIndex+=descrSize;
vocIndex=0;
}
}
todo el proceso es ahora 0,6, y los bucles de colada al comienzo son 0.001 segundos. Tal vez hice algo mal?
EDIT2: ¿Algo sobre Eigen? Cuando busco libs externas, siempre hablan sobre Eigen y su velocidad. Hice algo mal? Aquí un código simple usando Eigen que muestra que no es tan rápido. Tal vez me falta alguna configuración o alguna bandera, o ...
MatrixXd A = MatrixXd::Random(1000, 1000);
MatrixXd B = MatrixXd::Random(1000, 500);
MatrixXd X;
Este código es de aproximadamente 0.9 segundos.
¿Ha compilado todas las pruebas en modo de lanzamiento? –
Puede resultarle molesto que Matlab supere el rendimiento de su código, pero yo, que uso mucho Matlab, lo encuentro muy satisfactorio. No tengo muchos consejos concretos para usted, pero la clave para mejorar el rendimiento de este tipo de código suele ser hacer un uso óptimo (o al menos muy bueno) de la jerarquía de memoria en las CPU modernas. Otro factor a considerar es que gran parte de la funcionalidad principal de Matlab ahora tiene múltiples subprocesos para su ejecución en CPU de núcleos múltiples, no tengo claro que ninguno de sus propios códigos tenga múltiples hilos; eso probablemente tendría algún impacto en el rendimiento. –
No sé cómo ayudarte a hacer que tu código C/C++ sea más rápido (tu código se parece más a C que a C++. Evidencia: 'malloc') aún, pero puedo ver cómo puedes hacer que tu código Matlab sea más rápido: elimina el 'sqrt'. Dado dos números no negativos a y b, sqrt (a)> sqrt (b) ⇔ a> b. –