Как правильно многопоточность в OpenCV в 2019 году? - PullRequest
0 голосов
/ 31 января 2019

Справочная информация:

Я прочитал несколько статей и постов, касающихся многопоточности в OpenCV:

  • С одной стороны, вы можете создавать OpenCV с поддержкой TBB или OpenMP, которые распараллеливают функции OpenCV внутри.
  • С другой стороны, вы можете создавать несколько потоков самостоятельно и вызывать параллельные функции для реализации многопоточности на уровне приложения.

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

Относительно TBB, ответ от 2012 года с 5 ответами:

С WITH_TBB = ON OpenCV пытается использовать несколько потоков для некоторыхфункции.Проблема в том, что на данный момент только TBF имеет много функций (может быть дюжина).Так что трудно увидеть какое-либо ускорение.Философия OpenCV заключается в том, что приложение должно быть многопоточным, а не функциями OpenCV. [...]

Что касается многопоточности на уровне приложения, комментарий от модератора на answers.opencv.org :

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

Но другой ответ с 3 ответами гласит:

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

Описание проблемы:

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

После изучения этих проблем с производительностью я создал этот минимальный, полный и проверяемый пример кода:

#include "opencv2\opencv.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() function.
  • Я знаю, что создание потоков и их последующая очистка не будут бесплатными и будут медленнее, чем прямой вызов функции.
  • Это НЕ о том, что код вообще медленный. Проблема заключается в том, что напечатанные промежутки времени с течением времени становятся длиннее и длиннее .
  • Проблема не связана с функцией medianBlur(), поскольку она возникает в другихс другими функциями, такими как erode() или blur() тоже.
  • Проблема была воспроизведена под Mac под clang ++, см. комментарий @Mark Setchell
  • Проблема усиливается при использовании библиотеки отладки вместовыпуск

Моя среда тестирования:

  • Windows 10 64-битный
  • MSVC-компилятор
  • Официальные двоичные файлы OpenCV 3.4.2

Мои вопросы:

  • Можно ли использовать (мульти) многопоточность на уровне приложений с OpenCV?
  • Если да, почему интервалы времени печатаются моимпрограмма выше растет со временем?
  • Если нет, то почему OpenCV тогда считается потокобезопасным , и, пожалуйста, объясните, как интерпретировать оператор от Кирилла Корнякова вместо
  • Является ли TBB / OpenMP в 2019 году сейчасшироко поддерживается?
  • Если да, что обеспечивает лучшую производительность, многопоточность на уровне приложений (если разрешено) или TBB / OpenMP?

1 Ответ

0 голосов
/ 20 марта 2019

Прежде всего, спасибо за ясность вопроса.

Q: Можно ли использовать (multi) многопоточность на уровне приложений с OpenCV?

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

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

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

Ниже ответа потребовалось много исследований,спасибо, что задали вопрос, это действительно помогает мне добавить информацию к моим многопоточным знаниям:)

Q: Если да, почему промежутки времени напечатаны моей программой вышевремя?

A: После многих исследований я обнаружил, что создание и уничтожение потоков требует много ресурсов ЦП и памяти.Когда мы инициализируем поток (в вашем коде этой строкой: thread t(blurSlowdown, nullptr);), идентификатор записывается в область памяти, на которую указывает эта переменная, и этот идентификатор позволяет нам ссылаться на поток.Теперь в вашей программе вы создаете и уничтожаете потоки с очень высокой скоростью, теперь это то, что происходит, для программы выделен пул потоков, с помощью которого наша программа может запускать и уничтожать потоки, я буду кратким, и давайте посмотрим наобъяснение ниже:

  1. Когда вы создаете поток, это создает идентификатор, который указывает на этот поток.
  2. Когда вы уничтожаете поток, эта память освобождается

НО

При повторном создании потока, после того как первый поток не был уничтожен, идентификатор этого нового потока указывает на новое местоположение (местоположение, отличное от предыдущего потока) в пуле потоков.

После многократного создания и уничтожения потока, пул потоков исчерпан , и поэтому ЦП вынужден немного замедлять циклы нашей программы, так чтопул потоков снова освобождается для освобождения места для нового потока.

Intel TBB и OpenMP очень хорошо справляются с управлением пулами потоков, поэтому эта проблема может не возникать при их использовании.

Q: Широко ли поддерживается TBB в 2019 году?

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

Вот программа для реализации TBB в medianBlur:

#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 занимает около 10 мс, а без TBB - около40 мс.

Q: Если да, что обеспечивает лучшую производительность, многопоточность на уровне приложения (если разрешено) или TBB / OpenMP?

A: Я бы предложил использовать многопоточность TBB / OpenMP поверх POSIX (pthread / thread), потому что TBB предлагает вам лучший контроль над потоком + лучшую структуру для написания параллельного кода и внутренне он управляет pthreads.В случае, если вы используете pthreads, вам придется позаботиться о синхронизации, безопасности и т. Д. В вашем коде.Но использование этих рамок абстрагирует необходимость обработки потока, который может стать очень сложным.

Для сравнения TBB и проверки OpenMP этот ответ

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...