OpenCV 설치와 관련된 블로그를 읽다보면 TBB를 함께 설치하라는 글을 종종 찾을 수가 있습니다. TBB는 Threading Building Blocks의 약자이고, Intel에서 만든 다중 코어, 멀티 쓰레드 라이브러리입니다. 대용량 데이터인 영상을 처리할 때 여러 개의 쓰레드를 사용하여 연산을 좀 더 빠르게 수행할 수 있게 해주는 역할을 하는 라이브러리 입니다.

https://www.threadingbuildingblocks.org/

TBB는 예전에는 GPL 라이센스를 따르다가 TBB 2017 버전부터는 Apache v2.0 라이센스를 따르기 때문에 사용하기가 좀 더 자유로와졌습니다.


그렇다면 OpenCV를 직접 빌드하여 설치할 때 TBB를 함께 설치하면 효과적이겠지요? 멀티 쓰레드를 사용하여 연산 시간이 몇 배 빨라질 수 있으니까요. 그렇지만 굳이 번거롭게 TBB를 설치하지 않아도 최신 버전의 OpenCV에서는 멀티 쓰레드 프로그래밍을 기본 지원합니다. 오늘은 이 부분에 대해 정리하려고 합니다.


먼저 아래 링크를 살펴보면 이런 말이 쓰여 있습니다.

http://code.opencv.org/projects/opencv/wiki/ChangeLog#243


즉, OpenCV 2.4.3 버전부터 TBB 뿐만 아니라 OpenMP, GCD, Concurrency 같은 멀티 쓰레드 기법을 이용하여 OpenCV 코드가 동작한다는 의미입니다. 일단 저는 Windows 운영 체제에서 Visual Studio를 주로 이용하는데요, Visual Studio 2010 버전 이상부터는 MS의 Concurrency라는 이름의 멀티 쓰레드 기법이 기본으로 적용됩니다. 즉, 굳이 TBB를 깔지 않아도 Concurrency 기법을 통해 병렬 프로그래밍이 동작한다는 의미이죠. (몇몇 블로그에서 소개하는 TBB 설치 관련 얘기는 2013년 이전에 사용되는 기법입니다. 현재는 굳이 TBB를 사용하지 않아도 된다는 것이죠)


실제로 OpenCV 소스 코드 중에서 <opencv>/modules/core/src/Parallel.cpp 파일을 보면 아래와 같은 형태로 코드가 작성이 되어 있습니다.

void cv::parallel_for_(const cv::Range& range, const cv::ParallelLoopBody& body, double nstripes)
{
    CV_INSTRUMENT_REGION_MT_FORK()
    if (range.empty())
        return;

#ifdef CV_PARALLEL_FRAMEWORK

    if(numThreads != 0)
    {
        ProxyLoopBody pbody(body, range, nstripes);
        cv::Range stripeRange = pbody.stripeRange();
        if( stripeRange.end - stripeRange.start == 1 )
        {
            body(range);
            return;
        }

#if defined HAVE_TBB

        tbb::parallel_for(tbb::blocked_range(stripeRange.start, stripeRange.end), pbody);

#elif defined HAVE_CSTRIPES

        parallel(MAX(0, numThreads))
        {
            int offset = stripeRange.start;
            int len = stripeRange.end - offset;
            Range r(offset + CPX_RANGE_START(len), offset + CPX_RANGE_END(len));
            pbody(r);
            barrier();
        }

#elif defined HAVE_OPENMP

        #pragma omp parallel for schedule(dynamic)
        for (int i = stripeRange.start; i < stripeRange.end; ++i)
            pbody(Range(i, i + 1));

#elif defined HAVE_GCD

        dispatch_queue_t concurrent_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_apply_f(stripeRange.end - stripeRange.start, concurrent_queue, &pbody, block_function);

#elif defined WINRT

        Concurrency::parallel_for(stripeRange.start, stripeRange.end, pbody);

#elif defined HAVE_CONCURRENCY

        if(!pplScheduler || pplScheduler->Id() == Concurrency::CurrentScheduler::Id())
        {
            Concurrency::parallel_for(stripeRange.start, stripeRange.end, pbody);
        }
        else
        {
            pplScheduler->Attach();
            Concurrency::parallel_for(stripeRange.start, stripeRange.end, pbody);
            Concurrency::CurrentScheduler::Detach();
        }

#elif defined HAVE_PTHREADS_PF

        parallel_for_pthreads(pbody.stripeRange(), pbody, pbody.stripeRange().size());

#else

#error You have hacked and compiling with unsupported parallel framework

#endif

    }
    else

#endif // CV_PARALLEL_FRAMEWORK
    {
        (void)nstripes;
        body(range);
    }
}

즉, TBB, OpenMP, GCD, Concurrency, pthread 중 어느 것 하나만이라도 있으면 OpenCV는 멀티 쓰레드 형태로 동작을 하게 되고, 병렬 처리를 하기 때문에 빠르게 동작한다는 것이죠.


참고로 Visual Studio 버전이 2010 이상 버전이면 HAVE_CONCURRENCY가 자동으로 #define 됩니다. 라즈베리파이같은 리눅스 머신에서는 pthread가 자동으로 동작합니다.


그렇다면 TBB랑 OpenMP, Concurrency 같은 것들끼리 성능 차이가 얼마나 있을까요? 같은 병렬 프로그래밍이라고 하더라도 TBB가 더 효과적으로 잘 동작한다면 Visual Studio에서 기본 지원하는 Concurrency 보다는 TBB를 깔아서 쓰는 것이 더 좋지 않을까요?? 저도 이런 의문을 가지고 구글링을 열심히 해보았는데요, 뚜렷한 성능 비교 결과를 찾지 못했습니다. 그러나 좀 더 생각을 해보니, 어차피 OpenCV에서 큰 성능 향상을 얻으려면 병렬 프로그래밍보다는 CUDA 또는 OpenCL 쪽을 더 고민해보는 것이 맞지 않을까 생각합니다.


그래서 제 결론은, "Visual Studio 사용할 때에는 그냥 기본 Concurrency 라이브러리를 사용하자"입니다. 굳이 번거롭게 TBB를 깔지 않아도 됩니다. (TBB을 이용해서 빌드하면 프로그램 배포 시 tbb.dll 파일도 함께 배포해야 합니다.)


참고로 Visual Studio의 Concurrency에 대한 내용은 아래 유투브 동영상을 참고하시면 좋습니다. 아래 동영상에서 설명하는 Auto Parallelization이라는 것이 Concurrency와 같은 내용입니다.

https://youtu.be/dYx24RbQsY8



Posted by kkokkal
: