如何在2019年OpenCV中正确使用多线程
How to properly Multithread in OpenCV in 2019?
背景:
我在OpenCV:中阅读了一些关于多线程的文章和帖子
- 一方面,您可以构建具有TBB或OpenMP支持的OpenCV,这些支持在内部并行化OpenCV的功能
- 另一方面,您可以自己创建多个线程,并并行调用函数,以在应用程序级别实现多线程
但我无法得到一致的答案,哪种多线程方法是正确的。
关于TBB,2012年的答案是5票赞成:
With_TBB=ON OpenCV尝试为某些函数使用多个线程。问题是,目前只有相当多的函数与TBB线程连接(可能有十几个)。因此,很难看到任何加速。OpenCV的理念是应用程序应该是多线程的,而不是OpenCV函数。[…]
关于应用程序级的多线程,answers.opencv.org/上一位主持人的评论
请避免在opencv中使用您自己的多线程。许多函数显然不是线程安全的。而是使用TBB或openmp支持重建opencv-lib。
但另一个有3张赞成票的答案是:
库本身是线程安全的,因为您可以同时对库进行多个调用,但数据并不总是线程安全的。
问题描述:
所以我认为在应用程序级别使用(多)线程至少是可以的。但是,在长时间运行程序时,我遇到了一些奇怪的性能问题。
在研究了这些性能问题之后,我创建了这个最小的、完整的、可验证的示例代码:
#include "opencv2opencv.hpp"
#include <vector>
#include <chrono>
#include <thread>
using namespace cv;
using namespace std;
using namespace std::chrono;
void blurSlowdown(void*) {
Mat m1(360, 640, CV_8UC3);
Mat m2(360, 640, CV_8UC3);
medianBlur(m1, m2, 3);
}
int main()
{
for (;;) {
high_resolution_clock::time_point start = high_resolution_clock::now();
for (int k = 0; k < 100; k++) {
thread t(blurSlowdown, nullptr);
t.join(); //INTENTIONALLY PUT HERE READ PROBLEM DESCRIPTION
}
high_resolution_clock::time_point end = high_resolution_clock::now();
cout << duration_cast<microseconds>(end - start).count() << endl;
}
}
实际行为:
如果程序运行时间延长,打印的时间跨度
cout << duration_cast<microseconds>(end - start).count() << endl;
越来越大。
在运行程序大约10分钟后,打印的时间跨度增加了一倍,这是正常波动无法解释的。
预期行为:
我所期望的程序行为是时间跨度保持不变,即使它们可能比直接调用函数更长。
注意事项:
直接调用函数时:
[...]
for (int k = 0; k < 100; k++) {
blurSlowdown(nullptr);
}
[...]
打印的时间跨度保持不变。
不调用cv函数时:
void blurSlowdown(void*) {
Mat m1(360, 640, CV_8UC3);
Mat m2(360, 640, CV_8UC3);
//medianBlur(m1, m2, 3);
}
打印的时间跨度也保持不变。因此,在将线程与OpenCV函数结合使用时,一定存在问题。
- 我知道上面的代码并没有实现实际的多线程——在调用
blurSlowdown()
函数的同时,只有一个线程处于活动状态 - 我知道创建线程并在之后清理它们并不是免费的,而且会比直接调用函数慢
- 这是NOT关于代码通常很慢问题是打印的时间跨度随着时间的推移越来越长
- 这个问题与
medianBlur()
函数无关,因为它也发生在其他函数上,如erode()
或blur()
- 这个问题是在Mac的clang++下复制的,请参阅@Mark Setchell的评论
- 当使用调试库而不是版本时,问题会被放大
我的测试环境:
- Windows 10 64位
- MSVC编译器
- 官方OpenCV 3.4.2二进制文件
我的问题:
- 在OpenCV的应用程序级别上使用(多)线程可以吗
- 如果是,为什么我上面的程序打印的时间跨度会随着时间的推移而增长
- 如果没有,为什么OpenCV被认为是线程安全的,请解释如何解释Kirill Kornyakov的声明
- 2019年的TBB/OpenMP现在得到了广泛支持吗
- 如果是,是什么提供了更好的性能,应用程序级别的多线程(如果允许)还是TBB/OpenMP
首先,感谢您澄清问题。
Q:在OpenCV的应用程序级别上使用(多)线程可以吗?
A:是的,在OpenCV的应用程序级别上使用多线程是完全可以的,除非您使用可以利用多线程的功能,如模糊、颜色空间更改,在这里,您可以将图像分割为多个部分,并在分割的部分应用全局函数,然后重新组合以提供最终输出。
在一些函数中,如Hough、pca_sanalysis,当它们被应用于分割的图像部分然后重新组合时,不能给出正确的结果,在应用程序级别上对这些函数应用多线程可能不能给出正确结果,因此不应该这样做。
AsπάΓταῥεῖ前面提到,多线程的实现不会给您带来优势,因为您将线程加入for循环本身。我建议你使用promise和future对象(如果你想要一个如何使用的例子,请在评论中告诉我,我会分享这个片段
下面的答案花了很多研究,谢谢你提出这个问题,它真的帮助我为我的多线程知识添加信息:)
问:如果是,为什么我的程序打印的时间跨度会随着时间的推移而增长?
A:经过大量研究,我发现创建和销毁线程需要大量的CPU和内存资源。当我们初始化一个线程时(在您的代码中,用这一行:thread t(blurSlowdown, nullptr);
),一个标识符被写入这个变量所指向的内存位置,这个标识符使我们能够引用该线程。现在在你的程序中,你正在以非常高的速率创建和销毁线程,现在就是这样,有一个线程池分配给一个程序,我们的程序可以通过它运行和销毁线程
- 创建线程时,会创建一个指向该线程的标识符
- 当您破坏线程时,此内存将被释放
但是
-
在第一个线程未被破坏后再次创建线程时,此新线程的标识符指向线程池中的新位置(上一个线程以外的位置)。
-
在重复创建和销毁线程后,线程池将耗尽,因此CPU被迫放慢程序周期,以便再次释放线程池为新线程腾出空间。
Intel TBB和OpenMP非常擅长线程池管理,因此在使用它们时可能不会出现此问题。
问:2019年的TBB现在得到广泛支持吗?
A:是的,您可以在OpenCV程序中利用TBB,同时在构建OpenCV时启用TBB支持。
以下是在medianBlur:中实现TBB的程序
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <chrono>
using namespace cv;
using namespace std;
using namespace std::chrono;
class Parallel_process : public cv::ParallelLoopBody
{
private:
cv::Mat img;
cv::Mat& retVal;
int size;
int diff;
public:
Parallel_process(cv::Mat inputImgage, cv::Mat& outImage,
int sizeVal, int diffVal)
: img(inputImgage), retVal(outImage),
size(sizeVal), diff(diffVal)
{
}
virtual void operator()(const cv::Range& range) const
{
for(int i = range.start; i < range.end; i++)
{
/* divide image in 'diff' number
of parts and process simultaneously */
cv::Mat in(img, cv::Rect(0, (img.rows/diff)*i,
img.cols, img.rows/diff));
cv::Mat out(retVal, cv::Rect(0, (retVal.rows/diff)*i,
retVal.cols, retVal.rows/diff));
cv::medianBlur(in, out, size);
}
}
};
int main()
{
VideoCapture cap(0);
cv::Mat img, out;
while(1)
{
cap.read(img);
out = cv::Mat::zeros(img.size(), CV_8UC3);
// create 8 threads and use TBB
auto start1 = high_resolution_clock::now();
cv::parallel_for_(cv::Range(0, 8), Parallel_process(img, out, 9, 8));
//cv::medianBlur(img, out, 9); //Uncomment to compare time w/o TBB
auto stop1 = high_resolution_clock::now();
auto duration1 = duration_cast<microseconds>(stop1 - start1);
auto time_taken1 = duration1.count()/1000;
cout << "TBB Time: " << time_taken1 << "ms" << endl;
cv::imshow("image", img);
cv::imshow("blur", out);
cv::waitKey(1);
}
return 0;
}
在我的机器上,TBB的实现大约需要10ms,如果没有TBB,大约需要40ms。
问:如果是,什么提供更好的性能,应用程序级别的多线程(如果允许)或TBB/OpenMP?
A:我建议在POSIX多线程(pthread/thread)上使用TBB/OpenMP,因为TBB为您提供了对线程的更好控制+编写并行代码的更好结构,并且它在内部管理pthread。如果您使用pthreads,则必须注意代码中的同步和安全等问题。但是使用这些框架抽象了处理线程的需求,这可能会变得非常复杂。
编辑:我检查了有关图像尺寸与要分割处理的线程数不兼容的评论。因此,这里有一个潜在的解决方法(尚未测试,但应该有效),将图像分辨率缩放到兼容的尺寸,如:
如果您的图像分辨率是485 x 647,请将其缩放到488 x 648,然后将其传递给Parallel_process
,然后将输出缩小到458 x 647的原始大小。
为了比较TBB和OpenMP,请检查此答案
- 在C++中使用cURL和多线程
- 多线程双缓冲区
- 为什么我的多线程作业队列崩溃
- 在main()之外初始化std::vector会导致性能下降(多线程)
- 试图创建一个多线程程序来查找0-100000000之间的总素数
- 为什么一个向量上的多线程操作很慢
- 学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
- 全局变量 多读取器 一个写入器多线程安全?
- boost::文件系统::recursive_directory_iterator多线程安全
- 如何阻止TensorFlow的多线程
- 如何在多线程中正确使用unique_ptr进行多态性?
- 并发/多线程:是否可以以这种方式生成相同的输出?
- sigwait() 在多线程程序中不起作用
- 多线程程序中出现意外的内存泄漏
- 静态 constexpr 类成员变量对多线程读取是否安全?
- 多线程比没有线程C++慢
- 具有 C++11 多线程的特征库
- 通过安装信号处理程序关闭多线程应用程序
- 成员变量在多线程 C++ 时自行更改
- 如何在2019年OpenCV中正确使用多线程