opencv намного медленнее в многопоточности

-8

Im называет консольное приложение, использующее многопоточность. Каждый поток обрабатывает набор изображений, используя opencv-функции.

Если функция, которая использует функцию OpenCV выполняются в одном потоке я получаю ссылочное время вычислений. Если я выполняю эту функцию из нескольких потоков, функция (по отдельности в каждом потоке) работает намного медленнее (почти вдвое), когда она должна быть почти одинаковой.

¿Открывает ли opencv параллелизм, сериализует или блокирует выполнение?.

Я тестирую приложение, используя библиотеки opencv, скомпилированные WITH_TBB и без TBB, и результат почти такой же. Я не знаю, может ли это иметь какое-либо влияние, но я видел также, что некоторые функции, такие как cv :: threshold или cv :: findcontours, создают 12 дополнительных подпроцессов при выполнении beein. Если прокручиваются открытые вызовы cv, время для всех потоков одинаково и одинаково для полученных при выполнении одного потока, поэтому в этом случае многопоточность работает хорошо. Вопрос в том, есть ли опция компиляции opencv или вызов функции, который позволяет получить одно и то же время в многопоточности и в выполнении с одним потоком.

EDIT Это результат увеличения количества потоков (ядер) в CPU с четырьмя ядрами, выполняющих с 1, 2, 3 и 4 ядрами ту же функцию. Каждый основной процесс 768 изображений с разрешением 1600x1200 в цикле for. Внутри цикла вызывается функция, вызывающая увеличение задержки. Я ожидаю, что независимо от количества ядер время будет примерно таким же, как для одного потока (35000 мс), или на 10% больше, но, как видно, время увеличивается, когда число потоков увеличивается, я могу не найти почему...

TIMES: (Извините, система не позволяет загружать изображения на сообщения)

time in File No. 3 --> 35463
Mean time using 1 cores is: 47ms

time in File No. 3 --> 42747
time in File No. 3 --> 42709
Mean time using 2 cores is: 28ms

time in File No. 3 --> 54587
time in File No. 3 --> 54595
time in File No. 3 --> 54437
Mean time using 3 cores is: 24ms

time in File No. 3 --> 68751
time in File No. 3 --> 68865
time in File No. 3 --> 68878
time in File No. 3 --> 68622
Mean time using 4 cores is: 22ms

Если в функции не используется opencv-код, время, как и ожидалось, будет одинаковым для всех случаев 1, 2 3 или 4 потока, но когда используется открытая функция cv, например, только с простым вызовом:

img.convertTo(IMG, CV_32F);

beeing img a cv :: Mat, время увеличивается, когда число потоков увеличивается. Я сделал тест, также отключив опцию hiper-threading в CPU Bios. В этом случае все время уменьшалось, было время с 1 нитью 25 000 мс, но проблема увеличения времени все еще присутствует (33 секунды с 2 потоками, 43 с 3, 57 с 4)... Я не знаю, подсказывает ли это вам что нибудь

Изменить 2

Мекв:

#include "stdafx.h"
#include <future>
#include <chrono>
#include "Filter.h"
#include <iostream>
#include <future>

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

long long Ticks();
int WithOpencv(cv::Mat img);
int With_OUT_Opencv(cv::Mat img);
int TestThreads (char *buffer,std::string file);
#define Blur3x3(matrix,f,c) ((matrix[(f-1)*1600+(c-1)] + matrix[(f-1)*1600+c] + matrix[(f-1)*1600+(c+1)] + matrix[f*1600+(c-1)] + matrix[f*1600+c] + matrix[f*1600+(c+1)] + matrix[(f+1)*1600+(c-1)] + matrix[(f+1)*1600+c] + matrix[(f+1)*1600+(c+1)])/9)

int _tmain(int argc, _TCHAR* argv[])
{

std::string file="Test.bmp";

auto function = [&](char *buffer){return TestThreads(buffer,file);};
char *buffers[12];
std::future<int> frames[12];
DWORD tid;
int i,j;
int nframes = 0;
int ncores;

cv::setNumThreads(8);

for (i=0;i<8;i++) buffers[i] = new char[1000*1024*1024];
for (j=1;j<9;j++)
{
ncores = j;
long long t = Ticks();
for (i=0;i<ncores;i++) frames[i] = std::async(std::launch::async,function,buffers[i]);
for (i=0;i<ncores;i++) nframes += frames[i].get();
t = Ticks() - t;

std::cout << "Mean time using " << ncores << " cores is: " << t/nframes << "ms" << std::endl << std::endl;
nframes = 0;
Sleep(2000);
}
for (int i=0;i<8;i++) delete buffers[i];

return NULL;

return 0;
}

int TestThreads (char *buffer,std::string file)
{

long long ta;
int res;

char *ruta=new char[file.length() + 1];
strcpy(ruta,file.c_str());

cv::Mat img (1200, 1600, CV_8UC1);
img=cv::imread(file);

ta = Ticks();
for (int i=0;i<15;i++) {

//Uncomment this and comment next line to test without opencv calls. With_OUT_Opencv implements simple filters with direct operations over mat data
//res = With_OUT_Opencv(img);

res = WithOpencv(img);

}

ta = Ticks() - ta;
std::cout << "Time in file No. 3--> " << ta << std::endl;

return 15;
}

int WithOpencv(cv::Mat img){

cv::Mat img_bin;
cv::Mat img_filtered;
cv::Mat img_filtered2;
cv::Mat img_res;
int Crad_morf=2;
double Tthreshold=20;
cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(2*Crad_morf + 1, 2*Crad_morf+1));

img.convertTo(img,CV_32F);
cv::blur(img, img_filtered, cv::Size(3, 3));
cv::blur(img.mul(img), img_filtered2, cv::Size(3, 3));
cv::sqrt(img_filtered2 - img_filtered.mul(img_filtered), img_res);
cv::normalize(img_res, img_res, 0.0, 1.0, cv::NORM_MINMAX);
img_res.convertTo(img_res,CV_8UC1,255.0);
cv::threshold(img_res, img_bin, Tthreshold, 255, cv::THRESH_BINARY);

if (Crad_morf!=0){
cv::dilate(img_bin, img_bin, element);
}

return 0;
}

int With_OUT_Opencv(cv::Mat img){

unsigned char *baux1 = new unsigned char[1600*1200];
unsigned short *baux2 = new unsigned short[1600*1200];
unsigned char max=0;
int f,c,i;
unsigned char threshold = 177;

for (f=1;f<1199;f++) // Bad Blur filters
{
for (c=1; c<1599; c++)
{
baux1[f*1600+c] = Blur3x3(img.data,f,c);
baux1[f*1600+c] = baux1[f*1600+c] * baux1[f*1600+c];
baux2[f*1600+c] = img.data[f*1600+c] * img.data[f*1600+c];
}
}
for (f=1;f<1199;f++)
{
for (c=1; c<1599; c++)
{
baux1[f*1600+c] = sqrt(Blur3x3(baux2,f,c) - baux1[f*1600+c]);
if (baux1[f*1600+c] > max) max = baux1[f*1600+c];
}
}
threshold = threshold * ((float)max/255.0); // Bad Norm/Bin
for (i=0;i<1600*1200;i++)
{
if (baux1[i]>threshold) baux1[i] = 1;
else baux1[i] = 0;
}

delete []baux1;
delete []baux2;

return 0;
}

long long Ticks()
{
static long long last = 0;
static unsigned ticksPerMS = 0;
LARGE_INTEGER largo;

if (last==0)
{
QueryPerformanceFrequency(&largo);
ticksPerMS = (unsigned)(largo.QuadPart/1000);
QueryPerformanceCounter(&largo);
last = largo.QuadPart;
return 0;
}
QueryPerformanceCounter(&largo);
return (largo.QuadPart-last)/ticksPerMS;
}

спросил(а) 2021-01-25T19:52:13+03:00 4 месяца, 4 недели назад
1
Решение
63

Я в замешательстве по поводу вашего вопроса.

В вашем первоначальном вопросе было указано, что запуск x числа итераций в серийном режиме значительно быстрее, чем запуск их параллельно. Примечание: когда используется одна и та же целевая функция. И вам интересно, почему запуск одной и той же целевой функции значительно медленнее в многопоточном сценарии.

Однако теперь я вижу, что ваш пример сравнивает производительность OpenCV с другим другим кодом. Это ваш вопрос?

Связанный с вопросом, как я изначально думал, что вопрос был, ответ: нет, запуск целевой функции в серийном режиме не намного быстрее, чем запуск его параллельно. См. Результаты и код ниже.

Результаты


eight threads took 4104.38 ms
single thread took 7272.68 ms
four threads took 3687 ms
two threads took 4500.15 ms

(на Apple MBA 2012 i5 и opencv3)

Тестовый код

#include <iostream>
#include <vector>
#include <chrono>
#include <thread>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace std::chrono;
using namespace cv;

class benchmark {
time_point<steady_clock> start = steady_clock::now();
string title;
public:
benchmark(const string& title) : title(title) {}

~benchmark() {
auto diff = steady_clock::now() - start;
cout << title << " took " << duration <double, milli> (diff).count() << " ms" << endl;
}
};

template <typename F>
void repeat(unsigned n, F f) {
while (n--) f();
};

int targetFunction(Mat img){
cv::Mat img_bin;
cv::Mat img_filtered;
cv::Mat img_filtered2;
cv::Mat img_res;
int Crad_morf=2;
double Tthreshold=20;
cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(2*Crad_morf + 1, 2*Crad_morf+1));

img.convertTo(img,CV_32F);
cv::blur(img, img_filtered, cv::Size(3, 3));
cv::blur(img.mul(img), img_filtered2, cv::Size(3, 3));
cv::sqrt(img_filtered2 - img_filtered.mul(img_filtered), img_res);
cv::normalize(img_res, img_res, 0.0, 1.0, cv::NORM_MINMAX);
img_res.convertTo(img_res,CV_8UC1,255.0);
cv::threshold(img_res, img_bin, Tthreshold, 255, cv::THRESH_BINARY);

if (Crad_morf!=0){
cv::dilate(img_bin, img_bin, element);
}

//imshow("WithOpencv", img_bin);

return 0;
}

void runTargetFunction(int nIterations, int nThreads, const Mat& img) {
int nIterationsPerThread = nIterations / nThreads;
vector<thread> threads;
auto targetFunctionFn = [&img]() {
targetFunction(img);
};

setNumThreads(nThreads);

repeat(nThreads, [&] {
threads.push_back(thread([=]() {
repeat(nIterationsPerThread, targetFunctionFn);
}));
});

for(auto& thread : threads)
thread.join();
}

int main(int argc, const char * argv[]) {
string file = "../../opencv-test/Test.bmp";
auto img = imread(file);

const int nIterations = 64;

// let run using eight threads
{
benchmark b("eight threads");
runTargetFunction(nIterations, 8, img);
}

// let run using a single thread
{
benchmark b("single thread");
runTargetFunction(nIterations, 1, img);
}

// let run using four threads
{
benchmark b("four threads");
runTargetFunction(nIterations, 4, img);
}

// let run using a two threads
{
benchmark b("two threads");
runTargetFunction(nIterations, 2, img);
}

return 0;
}

ответил(а) 2021-01-25T19:52:13+03:00 4 месяца, 4 недели назад
63

Вы измеряете три вещи:

    Время, когда все потоки должны выполнять всю задачу, разделенную на размер всей задачи. Время, необходимое каждому отдельному потоку для выполнения своей части задачи. Время, необходимое для выполнения всей задачи.

Вы наблюдаете, что первый раз снижается с 47 мс до 22 мс при увеличении количества потоков. Это хорошо! В то же время вы понимаете, что время, требуемое отдельной нитью, увеличивается с 35463 до 68751 (независимо от единиц). Наконец, вы понимаете, что общее время выполнения увеличивается.

Что касается второго измерения: при увеличении количества потоков отдельные потоки требуют больше времени для выполнения соответствующих операций. Два возможных объяснения:

    Ваши потоки конкурируют за пропускную способность шины памяти. Ваши потоки запускают вычисления, которые многопоточны сами по себе, поэтому они эффективно конкурируют друг с другом за процессорное время.

Теперь на вопрос, почему общее рабочее время увеличивается. Причина проста: вы не только увеличиваете количество потоков, но и увеличиваете рабочую нагрузку с той же скоростью. Если ваши потоки не конкурируют друг с другом вообще, и не будет никаких накладных расходов, N потоков потребует того же времени, чтобы выполнить N раз всю работу. Это не так, поэтому вы заметили замедление.

ответил(а) 2021-01-25T19:52:13+03:00 4 месяца, 4 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема