【问题标题】:OpenCV - Basic Operations - Performance Issue [in Mode: Release]OpenCV - 基本操作 - 性能问题 [in Mode: Release]
【发布时间】:2017-08-19 20:02:13
【问题描述】:

我可能发现 OpenCV 自己的矩阵乘法/求和实现存在巨大的性能问题,如果我可能遗漏了什么,我想与你们核实一下:

提前:所有运行均在(OpenCV 的)发布模式下完成。

设置:

(a) 我将用一个 3×3 矩阵和一个 3×1 向量进行 1000 万次矩阵向量乘法。实现如下代码: res = mat * vec;

(b) 我将在我自己的实现中执行相同的操作,即分别访问元素,然后使用指针算术执行乘法过程。 [基本上只是将过程相乘并写下结果向量的每一行的方程]

我使用编译器标志 -O0、-O1、-O2、-O3、-Ofast 和 OpenCV 3.1 和 3.2 测试了这些变体。

计时是在 Ubuntu 16.04 上使用 chrono (high_resolution_clock) 完成的。

调查结果:

在所有情况下,非优化方法 (b) 的性能都优于 OpenCV 方法 (a) 约 100 到约 1000 倍。

问题:

怎么会这样呢? OpenCV 不应该针对这些类型的程序进行优化吗?我应该在 Github 上提出问题,还是我完全遗漏了什么?

代码:[准备在您的机器上复制和测试]

#include <chrono>
#include <iostream>

#include "opencv2/core/cvstd.hpp"
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"



int main()
{

    // 1. Setup:

    std::vector<std::chrono::high_resolution_clock::time_point> timestamp_vec_start(2);
    std::vector<std::chrono::high_resolution_clock::time_point> timestamp_vec_end(2);
    std::vector<double> timestamp_vec_total(2);


    cv::Mat test_mat = (cv::Mat_<float>(3,3) <<  0.023, 232.33, 0.545, 
                                                 22.22, 0.1123, 4.444,
                                                 0.012, 3.4521, 0.202);

    cv::Mat test_vec = (cv::Mat_<float>(3,1) <<  5.77, 
                                                 1.20,
                                                 0.03);

    cv::Mat result_1 = cv::Mat(3, 1, CV_32FC1);
    cv::Mat result_2 = cv::Mat(3, 1, CV_32FC1);

    cv::Mat temp_test_mat_results = cv::Mat(3, 3, CV_32FC1);
    cv::Mat temp_test_vec_results = cv::Mat(3, 1, CV_32FC1);

    auto ptr_test_mat_res_0 = temp_test_mat_results.ptr<float>(0);
    auto ptr_test_mat_res_1 = temp_test_mat_results.ptr<float>(1);
    auto ptr_test_mat_res_2 = temp_test_mat_results.ptr<float>(2);

    auto ptr_test_vec_res_0 = temp_test_vec_results.ptr<float>(0);
    auto ptr_test_vec_res_1 = temp_test_vec_results.ptr<float>(1);
    auto ptr_test_vec_res_2 = temp_test_vec_results.ptr<float>(2);

    auto ptr_res_0 = result_2.ptr<float>(0);
    auto ptr_res_1 = result_2.ptr<float>(1);
    auto ptr_res_2 = result_2.ptr<float>(2);





    // 2. OpenCV Basic Matrix Operations:

    timestamp_vec_start[0] = std::chrono::high_resolution_clock::now();

    for(int i = 0; i < 10000000; ++i)
    {
        // factor of up to 5000 here:
        // result_1 = (test_mat + test_mat + test_mat) * (test_vec + test_vec);

        // factor of 30~100 here:
        result_1 = test_mat * test_vec;
    }

    timestamp_vec_end[0]   = std::chrono::high_resolution_clock::now();
    timestamp_vec_total[0] = static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(timestamp_vec_end[0] - timestamp_vec_start[0]).count());





    // 3. Pixel-Wise Operations:

    timestamp_vec_start[1] = std::chrono::high_resolution_clock::now();

    for(int i = 0; i < 10000000; ++i)
    {
        auto ptr_test_mat_0 = test_mat.ptr<float>(0);
        auto ptr_test_mat_1 = test_mat.ptr<float>(1);
        auto ptr_test_mat_2 = test_mat.ptr<float>(2);

        auto ptr_test_vec_0 = test_vec.ptr<float>(0);
        auto ptr_test_vec_1 = test_vec.ptr<float>(1);
        auto ptr_test_vec_2 = test_vec.ptr<float>(2);


        ptr_test_mat_res_0[0] = ptr_test_mat_0[0] + ptr_test_mat_0[0] + ptr_test_mat_0[0];
        ptr_test_mat_res_0[1] = ptr_test_mat_0[1] + ptr_test_mat_0[1] + ptr_test_mat_0[1];
        ptr_test_mat_res_0[2] = ptr_test_mat_0[2] + ptr_test_mat_0[2] + ptr_test_mat_0[2];

        ptr_test_mat_res_1[0] = ptr_test_mat_1[0] + ptr_test_mat_1[0] + ptr_test_mat_1[0];
        ptr_test_mat_res_1[1] = ptr_test_mat_1[1] + ptr_test_mat_1[1] + ptr_test_mat_1[1];
        ptr_test_mat_res_1[2] = ptr_test_mat_1[2] + ptr_test_mat_1[2] + ptr_test_mat_1[2];

        ptr_test_mat_res_2[0] = ptr_test_mat_2[0] + ptr_test_mat_2[0] + ptr_test_mat_2[0];
        ptr_test_mat_res_2[1] = ptr_test_mat_2[1] + ptr_test_mat_2[1] + ptr_test_mat_2[1];
        ptr_test_mat_res_2[2] = ptr_test_mat_2[2] + ptr_test_mat_2[2] + ptr_test_mat_2[2];

        ptr_test_vec_res_0[0] = ptr_test_vec_0[0] + ptr_test_vec_0[0];
        ptr_test_vec_res_1[0] = ptr_test_vec_1[0] + ptr_test_vec_1[0];
        ptr_test_vec_res_2[0] = ptr_test_vec_2[0] + ptr_test_vec_2[0];

        ptr_res_0[0] = ptr_test_mat_res_0[0]*ptr_test_vec_res_0[0] + ptr_test_mat_res_0[1]*ptr_test_vec_res_1[0] + ptr_test_mat_res_0[2]*ptr_test_vec_res_2[0];
        ptr_res_1[0] = ptr_test_mat_res_1[0]*ptr_test_vec_res_0[0] + ptr_test_mat_res_1[1]*ptr_test_vec_res_1[0] + ptr_test_mat_res_1[2]*ptr_test_vec_res_2[0];
        ptr_res_2[0] = ptr_test_mat_res_2[0]*ptr_test_vec_res_0[0] + ptr_test_mat_res_2[1]*ptr_test_vec_res_1[0] + ptr_test_mat_res_2[2]*ptr_test_vec_res_2[0];
    }

    timestamp_vec_end[1]   = std::chrono::high_resolution_clock::now();
    timestamp_vec_total[1] = static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(timestamp_vec_end[1] - timestamp_vec_start[1]).count());





    // 4. Printout Timing Results:

    std::cout << "\n\nTimings:\n\n";
    std::cout << "Time spent in OpenCV's implementation:      "  << timestamp_vec_total[0]/1000.0 << " ms.\n";
    std::cout << "Time spent in element-wise implementation:  "  << timestamp_vec_total[1]/1000.0 << " ms.\n\n";

    std::cin.get();

    return 0;
}

【问题讨论】:

  • test_vec的初始化好像有错别字——有1,20——不应该是1.20吗?
  • 嘿,丹,感谢您指出这一点。快速修正了错字,并再次测试了代码。但是,结果保持不变。 [但是很奇怪为什么矩阵乘法首先起作用,而且我没有收到任何编译器警告,因为有这个错字......哈哈]
  • 我认为部分问题在于您正在比较的代码并不完全等效。例如,您的实现利用了本质上是一对全局临时变量的优势,因此它不会是可重入的。试着把它移到单独的函数中,看看会发生什么。
  • opencv 版本分配一堆,有函数调用开销。如果您想消除复制开销,请考虑使用cv::gemm。但老实说 OpenCV 并没有针对小矩阵乘法进行优化,请考虑使用类似 Eigen 的东西
  • 另外,您自定义的矩阵乘法实现不正确。比较结果。 |我会避免写ptr_test_vec_0[0] + ptr_test_vec_0[0] 而不是简单的ptr_test_vec_0[0] * 2。任何体面的编译器都可以为此生成最佳代码。

标签: c++ performance opencv optimization performance-testing


【解决方案1】:

OpenCV 没有针对小矩阵运算进行优化。
通过使用cv::gemm

,您可以通过不为循环内的结果分配新矩阵来稍微减少开销

但如果小矩阵运算对您来说是一个瓶颈,我建议您使用Eigen

使用快速的 Eigen 实现,例如:

Eigen::Matrix3d mat;
mat << 0.023, 232.33, 0.545,
    22.22, 0.1123, 4.444,
    0.012, 3.4521, 0.202;

Eigen::Vector3d vec3;
vec3 << 5.77,
    1.20,
    0.03;

Eigen::Vector3d result_e;

for (int i = 0; i < 10000000; ++i)
{
    result_e = (mat *3 ) * (vec3 *2);
}

用 VS2015 给我以下数字(显然,在 GCC 或 Clang 中差异可能不那么显着):

Timings:

Time spent in OpenCV's implementation:      2384.45 ms.
Time spent in element-wise implementation:  78.653 ms.
Time spent in Eigen implementation:         36.088 ms.

【讨论】:

    猜你喜欢
    • 2023-03-17
    • 2013-10-17
    • 1970-01-01
    • 2013-06-09
    • 1970-01-01
    • 1970-01-01
    • 2017-02-18
    • 2021-06-15
    • 2013-09-09
    相关资源
    最近更新 更多