Visual Servoing Platform  version 3.3.0 under development (2020-02-17)
perfMatrixMultiplication.cpp
1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2019 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See http://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31  * Description:
32  * Benchmark matrix multiplication.
33  *
34  *****************************************************************************/
35 
36 #include <visp3/core/vpConfig.h>
37 
38 #ifdef VISP_HAVE_CATCH2
39 #define CATCH_CONFIG_ENABLE_BENCHMARKING
40 #define CATCH_CONFIG_RUNNER
41 #include <catch.hpp>
42 
43 #include <visp3/core/vpMatrix.h>
44 
45 #if (VISP_HAVE_OPENCV_VERSION >= 0x030000)
46 #include <opencv2/core.hpp>
47 #endif
48 
49 #ifdef VISP_HAVE_EIGEN3
50 #include <Eigen/Dense>
51 #endif
52 
53 namespace
54 {
55 
56 bool runBenchmark = false;
57 
58 double getRandomValues(double min, double max)
59 {
60  return (max - min) * ((double)rand() / (double)RAND_MAX) + min;
61 }
62 
63 vpMatrix generateRandomMatrix(unsigned int rows, unsigned int cols, double min=-1, double max=1)
64 {
65  vpMatrix M(rows, cols);
66 
67  for (unsigned int i = 0; i < M.getRows(); i++) {
68  for (unsigned int j = 0; j < M.getCols(); j++) {
69  M[i][j] = getRandomValues(min, max);
70  }
71  }
72 
73  return M;
74 }
75 
76 vpColVector generateRandomVector(unsigned int rows, double min=-1, double max=1)
77 {
78  vpColVector v(rows);
79 
80  for (unsigned int i = 0; i < v.getRows(); i++) {
81  v[i] = getRandomValues(min, max);
82  }
83 
84  return v;
85 }
86 
87 // Copy of vpMatrix::mult2Matrices
88 vpMatrix dgemm_regular(const vpMatrix& A, const vpMatrix& B)
89 {
90  vpMatrix C;
91 
92  if ((A.getRows() != C.getRows()) || (B.getCols() != C.getCols())) {
93  C.resize(A.getRows(), B.getCols(), false);
94  }
95 
96  if (A.getCols() != B.getRows()) {
97  throw(vpException(vpException::dimensionError, "Cannot multiply (%dx%d) matrix by (%dx%d) matrix", A.getRows(),
98  A.getCols(), B.getRows(), B.getCols()));
99  }
100 
101  // 5/12/06 some "very" simple optimization to avoid indexation
102  unsigned int BcolNum = B.getCols();
103  unsigned int BrowNum = B.getRows();
104  unsigned int i, j, k;
105  for (i = 0; i < A.getRows(); i++) {
106  double *rowptri = A[i];
107  double *ci = C[i];
108  for (j = 0; j < BcolNum; j++) {
109  double s = 0;
110  for (k = 0; k < BrowNum; k++) {
111  s += rowptri[k] * B[k][j];
112  }
113  ci[j] = s;
114  }
115  }
116 
117  return C;
118 }
119 
120 // Copy of vpMatrix::AtA
121 vpMatrix AtA_regular(const vpMatrix& A)
122 {
123  vpMatrix B;
124  B.resize(A.getCols(), A.getCols(), false);
125 
126  unsigned int i, j, k;
127  double s;
128  double *ptr;
129  for (i = 0; i < A.getCols(); i++) {
130  double *Bi = B[i];
131  for (j = 0; j < i; j++) {
132  ptr = A.data;
133  s = 0;
134  for (k = 0; k < A.getRows(); k++) {
135  s += (*(ptr + i)) * (*(ptr + j));
136  ptr += A.getCols();
137  }
138  *Bi++ = s;
139  B[j][i] = s;
140  }
141  ptr = A.data;
142  s = 0;
143  for (k = 0; k < A.getRows(); k++) {
144  s += (*(ptr + i)) * (*(ptr + i));
145  ptr += A.getCols();
146  }
147  *Bi = s;
148  }
149 
150  return B;
151 }
152 
153 // Copy of vpMatrix::multMatrixVector
154 vpColVector dgemv_regular(const vpMatrix& A, const vpColVector& v)
155 {
156  vpColVector w;
157 
158  if (A.getCols() != v.getRows()) {
159  throw(vpException(vpException::dimensionError, "Cannot multiply a (%dx%d) matrix by a (%d) column vector",
160  A.getRows(), A.getCols(), v.getRows()));
161  }
162 
163  w.resize(A.getRows(), true);
164 
165  for (unsigned int j = 0; j < A.getCols(); j++) {
166  double vj = v[j]; // optimization em 5/12/2006
167  for (unsigned int i = 0; i < A.getRows(); i++) {
168  w[i] += A[i][j] * vj;
169  }
170  }
171 
172  return w;
173 }
174 
175 // Copy of vpMatrix::operator*(const vpVelocityTwistMatrix &V)
176 vpMatrix mat_mul_twist_matrix(const vpMatrix& A, const vpVelocityTwistMatrix& V)
177 {
178  vpMatrix M;
179 
180  if (A.getCols() != V.getRows()) {
181  throw(vpException(vpException::dimensionError, "Cannot multiply (%dx%d) matrix by (6x6) velocity twist matrix",
182  A.getRows(), A.getCols()));
183  }
184 
185  M.resize(A.getRows(), 6, false);
186 
187  unsigned int VcolNum = V.getCols();
188  unsigned int VrowNum = V.getRows();
189 
190  for (unsigned int i = 0; i < A.getRows(); i++) {
191  double *rowptri = A[i];
192  double *ci = M[i];
193  for (unsigned int j = 0; j < VcolNum; j++) {
194  double s = 0;
195  for (unsigned int k = 0; k < VrowNum; k++)
196  s += rowptri[k] * V[k][j];
197  ci[j] = s;
198  }
199  }
200 
201  return M;
202 }
203 
204 bool equalMatrix(const vpMatrix& A, const vpMatrix& B, double tol=1e-9)
205 {
206  if (A.getRows() != B.getRows() || A.getCols() != B.getCols()) {
207  return false;
208  }
209 
210  for (unsigned int i = 0; i < A.getRows(); i++) {
211  for (unsigned int j = 0; j < A.getCols(); j++) {
212  if (!vpMath::equal(A[i][j], B[i][j], tol)) {
213  return false;
214  }
215  }
216  }
217 
218  return true;
219 }
220 
221 }
222 
223 TEST_CASE("Benchmark matrix-matrix multiplication", "[benchmark]") {
224  if (runBenchmark) {
225  std::vector<std::pair<int, int>> sizes = { {6, 200}, {200, 6}, {207, 119}, {83, 201}, {600, 400}, {400, 600} };
226 
227  for (auto sz : sizes) {
228  vpMatrix A = generateRandomMatrix(sz.first, sz.second);
229  vpMatrix B = generateRandomMatrix(sz.second, sz.first);
230 
231  std::ostringstream oss;
232  oss << "(" << A.getRows() << "x" << A.getCols() << ")x(" << B.getRows() << "x" << B.getCols() << ") - Naive code";
233  BENCHMARK(oss.str().c_str()) {
234  vpMatrix C = dgemm_regular(A, B);
235  return C;
236  };
237 
238  oss.str("");
239  oss << "(" << A.getRows() << "x" << A.getCols() << ")x(" << B.getRows() << "x" << B.getCols() << ") - ViSP";
240  BENCHMARK(oss.str().c_str()) {
241  vpMatrix C = A * B;
242  return C;
243  };
244  {
245  vpMatrix C_true = dgemm_regular(A, B);
246  vpMatrix C = A * B;
247  REQUIRE(equalMatrix(C, C_true));
248  }
249 
250 #if (VISP_HAVE_OPENCV_VERSION >= 0x030000)
251  cv::Mat matA(sz.first, sz.second, CV_64FC1);
252  cv::Mat matB(sz.second, sz.first, CV_64FC1);
253 
254  for (unsigned int i = 0; i < A.getRows(); i++) {
255  for (unsigned int j = 0; j < A.getCols(); j++) {
256  matA.at<double>(i, j) = A[i][j];
257  matB.at<double>(j, i) = B[j][i];
258  }
259  }
260 
261  oss.str("");
262  oss << "(" << matA.rows << "x" << matA.cols << ")x(" << matB.rows << "x" << matB.cols << ") - OpenCV";
263  BENCHMARK(oss.str().c_str()) {
264  cv::Mat matC = matA * matB;
265  return matC;
266  };
267 #endif
268 
269 #ifdef VISP_HAVE_EIGEN3
270  Eigen::MatrixXd eigenA(sz.first, sz.second);
271  Eigen::MatrixXd eigenB(sz.second, sz.first);
272 
273  for (unsigned int i = 0; i < A.getRows(); i++) {
274  for (unsigned int j = 0; j < A.getCols(); j++) {
275  eigenA(i, j) = A[i][j];
276  eigenB(j, i) = B[j][i];
277  }
278  }
279 
280  oss.str("");
281  oss << "(" << eigenA.rows() << "x" << eigenA.cols() << ")x(" << eigenB.rows() << "x" << eigenB.cols() << ") - Eigen";
282  BENCHMARK(oss.str().c_str()) {
283  Eigen::MatrixXd eigenC = eigenA * eigenB;
284  return eigenC;
285  };
286 #endif
287  }
288  }
289 
290  {
291  const unsigned int rows = 47, cols = 63;
292  vpMatrix A = generateRandomMatrix(rows, cols);
293  vpMatrix B = generateRandomMatrix(cols, rows);
294 
295  vpMatrix C_true = dgemm_regular(A, B);
296  vpMatrix C = A * B;
297  REQUIRE(equalMatrix(C, C_true));
298  }
299 }
300 
301 TEST_CASE("Benchmark matrix-vector multiplication", "[benchmark]") {
302  if (runBenchmark) {
303  std::vector<std::pair<int, int>> sizes = { {6, 200}, {200, 6}, {207, 119}, {83, 201}, {600, 400}, {400, 600} };
304 
305  for (auto sz : sizes) {
306  vpMatrix A = generateRandomMatrix(sz.first, sz.second);
307  vpColVector B = generateRandomVector(sz.second);
308 
309  std::ostringstream oss;
310  oss << "(" << A.getRows() << "x" << A.getCols() << ")x(" << B.getRows() << "x" << B.getCols() << ") - Naive code";
311  BENCHMARK(oss.str().c_str()) {
312  vpColVector C = dgemv_regular(A, B);
313  return C;
314  };
315 
316  oss.str("");
317  oss << "(" << A.getRows() << "x" << A.getCols() << ")x(" << B.getRows() << "x" << B.getCols() << ") - ViSP";
318  BENCHMARK(oss.str().c_str()) {
319  vpColVector C = A * B;
320  return C;
321  };
322  {
323  vpColVector C_true = dgemv_regular(A, B);
324  vpColVector C = A * B;
325  REQUIRE(equalMatrix(C, C_true));
326  }
327 
328 #if (VISP_HAVE_OPENCV_VERSION >= 0x030000)
329  cv::Mat matA(sz.first, sz.second, CV_64FC1);
330  cv::Mat matB(sz.second, 1, CV_64FC1);
331 
332  for (unsigned int i = 0; i < A.getRows(); i++) {
333  for (unsigned int j = 0; j < A.getCols(); j++) {
334  matA.at<double>(i, j) = A[i][j];
335  if (i == 0) {
336  matB.at<double>(j, 0) = B[j];
337  }
338  }
339  }
340 
341  oss.str("");
342  oss << "(" << matA.rows << "x" << matA.cols << ")x(" << matB.rows << "x" << matB.cols << ") - OpenCV";
343  BENCHMARK(oss.str().c_str()) {
344  cv::Mat matC = matA * matB;
345  return matC;
346  };
347 #endif
348 
349 #ifdef VISP_HAVE_EIGEN3
350  Eigen::MatrixXd eigenA(sz.first, sz.second);
351  Eigen::MatrixXd eigenB(sz.second, 1);
352 
353  for (unsigned int i = 0; i < A.getRows(); i++) {
354  for (unsigned int j = 0; j < A.getCols(); j++) {
355  eigenA(i, j) = A[i][j];
356  if (i == 0) {
357  eigenB(j, 0) = B[j];
358  }
359  }
360  }
361 
362  oss.str("");
363  oss << "(" << eigenA.rows() << "x" << eigenA.cols() << ")x(" << eigenB.rows() << "x" << eigenB.cols() << ") - Eigen";
364  BENCHMARK(oss.str().c_str()) {
365  Eigen::MatrixXd eigenC = eigenA * eigenB;
366  return eigenC;
367  };
368 #endif
369  }
370  }
371 
372  {
373  const unsigned int rows = 47, cols = 63;
374  vpMatrix A = generateRandomMatrix(rows, cols);
375  vpColVector B = generateRandomVector(cols);
376 
377  vpColVector C_true = dgemv_regular(A, B);
378  vpColVector C = A * B;
379  REQUIRE(equalMatrix(C, C_true));
380  }
381 }
382 
383 TEST_CASE("Benchmark AtA", "[benchmark]") {
384  if (runBenchmark) {
385  std::vector<std::pair<int, int>> sizes = { {6, 200}, {200, 6}, {207, 119}, {83, 201}, {600, 400}, {400, 600} };
386 
387  for (auto sz : sizes) {
388  vpMatrix A = generateRandomMatrix(sz.first, sz.second);
389 
390  std::ostringstream oss;
391  oss << "(" << A.getRows() << "x" << A.getCols() << ") - Naive code";
392  BENCHMARK(oss.str().c_str()) {
393  vpMatrix AtA = AtA_regular(A);
394  return AtA;
395  };
396 
397  oss.str("");
398  oss << "(" << A.getRows() << "x" << A.getCols() << ") - ViSP";
399  BENCHMARK(oss.str().c_str()) {
400  vpMatrix AtA = A.AtA();
401  return AtA;
402  };
403  {
404  vpMatrix AtA_true = AtA_regular(A);
405  vpMatrix AtA = A.AtA();
406  REQUIRE(equalMatrix(AtA, AtA_true));
407  }
408 
409 #if (VISP_HAVE_OPENCV_VERSION >= 0x030000)
410  cv::Mat matA(sz.first, sz.second, CV_64FC1);
411 
412  for (unsigned int i = 0; i < A.getRows(); i++) {
413  for (unsigned int j = 0; j < A.getCols(); j++) {
414  matA.at<double>(i, j) = A[i][j];
415  }
416  }
417 
418  oss.str("");
419  oss << "(" << matA.rows << "x" << matA.cols << ") - OpenCV";
420  BENCHMARK(oss.str().c_str()) {
421  cv::Mat matAtA = matA.t() * matA;
422  return matAtA;
423  };
424 #endif
425 
426 #ifdef VISP_HAVE_EIGEN3
427  Eigen::MatrixXd eigenA(sz.first, sz.second);
428 
429  for (unsigned int i = 0; i < A.getRows(); i++) {
430  for (unsigned int j = 0; j < A.getCols(); j++) {
431  eigenA(i, j) = A[i][j];
432  }
433  }
434 
435  oss.str("");
436  oss << "(" << eigenA.rows() << "x" << eigenA.cols() << ") - Eigen";
437  BENCHMARK(oss.str().c_str()) {
438  Eigen::MatrixXd eigenAtA = eigenA.transpose() * eigenA;
439  return eigenAtA;
440  };
441 #endif
442  }
443  }
444 
445  {
446  const unsigned int rows = 47, cols = 63;
447  vpMatrix A = generateRandomMatrix(rows, cols);
448 
449  vpMatrix AtA_true = AtA_regular(A);
450  vpMatrix AtA = A.AtA();
451  REQUIRE(equalMatrix(AtA, AtA_true));
452  }
453 }
454 
455 TEST_CASE("Benchmark matrix-velocity twist multiplication", "[benchmark]") {
456  if (runBenchmark) {
457  std::vector<std::pair<int, int>> sizes = { {20, 6}, {207, 6}, {600, 6}, {1201, 6} };
458 
459  for (auto sz : sizes) {
460  vpMatrix A = generateRandomMatrix(sz.first, sz.second);
461  vpVelocityTwistMatrix V(vpTranslationVector(0.1, -0.4, 1.5), vpThetaUVector(0.4, -0.1, 0.7));
462 
463  std::ostringstream oss;
464  oss << "(" << A.getRows() << "x" << A.getCols() << ")x(6x6) - Naive code";
465  BENCHMARK(oss.str().c_str()) {
466  vpMatrix AV = mat_mul_twist_matrix(A, V);
467  return AV;
468  };
469 
470  oss.str("");
471  oss << "(" << A.getRows() << "x" << A.getCols() << ")x(6x6) - ViSP";
472  BENCHMARK(oss.str().c_str()) {
473  vpMatrix AV = A * V;
474  return AV;
475  };
476  {
477  vpMatrix AV_true = mat_mul_twist_matrix(A, V);
478  vpMatrix AV = A * V;
479  REQUIRE(equalMatrix(AV, AV_true));
480  }
481 
482 #if (VISP_HAVE_OPENCV_VERSION >= 0x030000)
483  cv::Mat matA(sz.first, sz.second, CV_64FC1);
484  cv::Mat matV(6, 6, CV_64FC1);
485 
486  for (unsigned int i = 0; i < A.getRows(); i++) {
487  for (unsigned int j = 0; j < A.getCols(); j++) {
488  matA.at<double>(i, j) = A[i][j];
489  }
490  }
491  for (unsigned int i = 0; i < V.getRows(); i++) {
492  for (unsigned int j = 0; j < V.getCols(); j++) {
493  matV.at<double>(i, j) = V[i][j];
494  }
495  }
496 
497  oss.str("");
498  oss << "(" << matA.rows << "x" << matA.cols << ")x(6x6) - OpenCV";
499  BENCHMARK(oss.str().c_str()) {
500  cv::Mat matAV = matA * matV;
501  return matAV;
502  };
503 #endif
504 
505 #ifdef VISP_HAVE_EIGEN3
506  Eigen::MatrixXd eigenA(sz.first, sz.second);
507  Eigen::MatrixXd eigenV(6, 6);
508 
509  for (unsigned int i = 0; i < A.getRows(); i++) {
510  for (unsigned int j = 0; j < A.getCols(); j++) {
511  eigenA(i, j) = A[i][j];
512  }
513  }
514  for (unsigned int i = 0; i < V.getRows(); i++) {
515  for (unsigned int j = 0; j < V.getCols(); j++) {
516  eigenV(i, j) = V[i][j];
517  }
518  }
519 
520  oss.str("");
521  oss << "(" << eigenA.rows() << "x" << eigenA.cols() << ")x(6x6) - Eigen";
522  BENCHMARK(oss.str().c_str()) {
523  Eigen::MatrixXd eigenAV = eigenA * eigenV;
524  return eigenAV;
525  };
526 #endif
527  }
528  }
529 
530  {
531  const unsigned int rows = 47, cols = 6;
532  vpMatrix A = generateRandomMatrix(rows, cols);
533  vpVelocityTwistMatrix V(vpTranslationVector(0.1, -0.4, 1.5), vpThetaUVector(0.4, -0.1, 0.7));
534 
535  vpMatrix AV_true = mat_mul_twist_matrix(A, V);
536  vpMatrix AV = A * V;
537  REQUIRE(equalMatrix(AV, AV_true));
538  }
539 }
540 
541 int main(int argc, char *argv[])
542 {
543  Catch::Session session; // There must be exactly one instance
544 
545  // Build a new parser on top of Catch's
546  using namespace Catch::clara;
547  auto cli = session.cli() // Get Catch's composite command line parser
548  | Opt(runBenchmark) // bind variable to a new option, with a hint string
549  ["--benchmark"] // the option names it will respond to
550  ("run benchmark?"); // description string for the help output
551 
552  // Now pass the new composite back to Catch so it uses that
553  session.cli(cli);
554 
555  // Let Catch (using Clara) parse the command line
556  session.applyCommandLine(argc, argv);
557 
558  int numFailed = session.run();
559 
560  // numFailed is clamped to 255 as some unices only use the lower 8 bits.
561  // This clamping has already been applied, so just return it here
562  // You can also do any post run clean-up here
563  return numFailed;
564 }
565 #else
566 #include <iostream>
567 
568 int main()
569 {
570  return 0;
571 }
572 #endif
Implementation of a matrix and operations on matrices.
Definition: vpMatrix.h:164
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
Definition: vpArray2D.h:305
vpMatrix AtA() const
Definition: vpMatrix.cpp:693
static bool equal(double x, double y, double s=0.001)
Definition: vpMath.h:296
error that can be emited by ViSP classes.
Definition: vpException.h:71
unsigned int getRows() const
Definition: vpArray2D.h:289
Type * data
Address of the first element of the data array.
Definition: vpArray2D.h:145
unsigned int getCols() const
Definition: vpArray2D.h:279
vpMatrix t() const
Definition: vpMatrix.cpp:507
vpMatrix transpose() const
Definition: vpMatrix.cpp:517
void resize(unsigned int i, bool flagNullify=true)
Definition: vpColVector.h:310
Implementation of column vector and the associated operations.
Definition: vpColVector.h:130
Class that consider the case of a translation vector.
Implementation of a rotation vector as axis-angle minimal representation.