
저는 Buildroot를 사용하여 Raspberry PI3용 Embedded Linux OS를 구축하고 있습니다. 이 OS는 여러 애플리케이션을 처리하는 데 사용되며 그 중 하나는 OpenCV(v3.3.0)를 기반으로 객체 감지를 수행합니다.
Raspbian Jessy + Python으로 시작했지만 간단한 예제를 실행하는 데 시간이 많이 걸리는 것으로 확인되어 Python 대신 최적화된 기능 + C++ 개발로 나만의 RTOS를 설계하기로 결정했습니다.
이러한 최적화를 통해 RPI 4코어 + 1GB RAM이 이러한 애플리케이션을 처리할 수 있을 것이라고 생각했습니다. 문제는 이런 것에도 불구하고 가장 간단한 컴퓨터 비전 프로그램이 시간이 많이 걸린다는 것입니다.
PC와 Raspberry PI3 비교
이것은 프로그램의 각 부분에 대한 실행 시간의 정도를 파악하기 위해 작성한 간단한 프로그램입니다.
#include <stdio.h>
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <time.h> /* clock_t, clock, CLOCKS_PER_SEC */
using namespace cv;
using namespace std;
int main()
{
setUseOptimized(true);
clock_t t_access, t_proc, t_save, t_total;
// Access time.
t_access = clock();
Mat img0 = imread("img0.jpg", IMREAD_COLOR);// takes ~90ms
t_access = clock() - t_access;
// Processing time
t_proc = clock();
cvtColor(img0, img0, CV_BGR2GRAY);
blur(img0, img0, Size(9,9));// takes ~18ms
t_proc = clock() - t_proc;
// Saving time
t_save = clock();
imwrite("img1.jpg", img0);
t_save = clock() - t_save;
t_total = t_access + t_proc + t_save;
//printf("CLOCKS_PER_SEC = %d\n\n", CLOCKS_PER_SEC);
printf("(TEST 0) Total execution time\t %d cycles \t= %f ms!\n", t_total,((float)t_total)*1000./CLOCKS_PER_SEC);
printf("---->> Accessing in\t %d cycles \t= %f ms.\n", t_access,((float)t_access)*1000./CLOCKS_PER_SEC);
printf("---->> Processing in\t %d cycles \t= %f ms.\n", t_proc,((float)t_proc)*1000./CLOCKS_PER_SEC);
printf("---->> Saving in\t %d cycles \t= %f ms.\n", t_save,((float)t_save)*1000./CLOCKS_PER_SEC);
return 0;
}
Raspberry PI에서 실행 결과(Buildroot에서 생성된 OS)
보시다시피 엄청난 차이가 있습니다. 나에게 필요한 것은 모든 단일 세부 사항을 최적화하여 이 예가처리단계는 "거의" 실시간으로 발생합니다.최대 15ms 시간 내에.
내 질문은 다음과 같습니다.
- 집중적인 계산 애플리케이션을 처리할 수 있도록 OS를 최적화하고 각 부분의 우선 순위를 어떻게 제어할 수 있습니까?
- 요구 사항을 충족하기 위해 RPI3의 4개 코어를 어떻게 완전히 사용할 수 있습니까?
- OpenCV 대신 다른 가능성이 있습니까?
- C++ 대신 C를 사용해야 합니까?
- 하드웨어 개선을 권장하시나요?
답변1
순서대로:
집중적인 계산 애플리케이션을 처리할 수 있도록 OS를 최적화하고 각 부분의 우선 순위를 어떻게 제어할 수 있습니까?
일반적인 최적화의 경우, 실제로 필요한 것만 백그라운드에서 실행되도록 하는 것과 같은 일반적인 작업 외에 OS 측에서 할 수 있는 일은 많지 않습니다. 원본 Pi에서는 해당 기능의 어셈블리 최적화 버전을 제공하는 'cofi'라는 라이브러리를 'ing memmove()
하여 유사한 기능의 속도를 높일 수 있지만 LD_PRELOAD
Pi 3에서는 도움이 될지 확실하지 않습니다.
우선 순위 지정에 대해서는 매뉴얼 페이지를 살펴봐야 하지만 일반적으로 병렬화하지 않는 한 이를 수행할 수 없습니다. (귀하의 경우 확실한 해결책은 각 단계를 프로세스에 따라 실행하고 IPC를 사용하는 것 같습니다(아마도 성능상의 이유로 공유 메모리) 간에 데이터를 이동합니다.
테스트 프로그램에서 인용한 결과를 보면 특히 Pi에서 처리 및 저장 단계가 모두 약 10배 느린 반면 액세스 단계는 약 5배만 느리며 그 숫자는 다음과 일치합니다. Pi 3를 1년 미만의 일반 PC와 비교할 때 예상되는 대략적인 추정치입니다. Pi의 CPU는 PC 테스트를 실행한 것보다 거의 확실하게 훨씬 느립니다. (그리고 전혀 병렬화하지 않은 경우 대부분의 최신 x86 CPU는 자체적으로 단일 코어를 실행할 수 있기 때문에 격차가 훨씬 더 넓어집니다. 모든 코어를 전체 로드로 실행할 수 있는 것보다 전체 로드가 훨씬 더 빠르며 이는 영향을 미칠 것입니다. ARM ISA는 x86 ISA와도 크게 다릅니다(ARM은 x86에 비해 주기당 작업이 적은 경향이 있지만 일반적으로 RAM에 자주 액세스할 필요가 없으며 일반적으로 x86만큼 분기 예측 실패로 인해 비용이 많이 들지 않습니다) , 따라서 GCC가 PC에서 작업을 정렬하는 방식에 최적화된 코드는 Pi에서 최적이 아닐 수 있습니다.
어떤 카메라를 사용하고 있는지는 모르겠지만 처리 중인 이미지의 해상도를 줄이면 더 나은 시간을 얻을 수 있을 것으로 기대하고 사용을 피하면 획득 시간을 줄일 수 있을 것입니다. 압축 형식(손실 압축을 사용하지 않는다는 것은 해상도가 그다지 중요하지 않다는 것을 의미합니다).
요구 사항을 충족하기 위해 RPI3의 4개 코어를 어떻게 완전히 사용할 수 있습니까?
자신의 코드에서 병렬화. 커널에서 SMP가 활성화되어 있는지 확인한 다음(RPi Foundation의 공식 구성을 사용하는 경우 활성화되어야 함) 병렬로 실행해 보십시오. OpenCV가 작업 자체를 얼마나 병렬화하는지 잘 모르겠지만 OpenMP도 살펴보는 것이 좋습니다(상호의존적이지 않은 루프에서 반복을 병렬화하는 비교적 쉬운 방법을 제공합니다).
OpenCV 대신 다른 가능성이 있습니까?
있을 수도 있지만 모든 사람이 OpenCV로 표준화되어 있으므로 이를 사용하는 것이 좋습니다(모든 사람이 OpenCV를 사용하기 때문에 구현에 대한 기술적인 도움을 받는 것이 더 쉬울 것입니다).
C++ 대신 C를 사용해야 합니까?
그것은 당신이 물건을 어떻게 사용하는지에 달려 있습니다. C보다 C++에서 느린 코드를 작성하는 것이 훨씬 쉽지만 두 언어 모두에서 빠른 코드를 작성하는 것은 더 이상 어렵지 않습니다. 많은 최적화 기술은 두 언어에서 매우 유사합니다(예를 들어 시작 시 모든 것을 미리 할당하여 malloc()
중요한 섹션에서 호출하지 않거나 호출을 피하는 것 stat()
). 하지만 특히 C++의 경우 std::string
전염병처럼 모든 곳에서 호출하여 결과적으로 엄청나게 느립니다. ( 어떤 경우에는 C 스타일 문자열 malloc()
로 전환하는 변환이 성능을 40% 이상 향상시키는 것을 보았습니다. std::string
).
하드웨어 개선을 권장하시나요?
하드웨어 비용을 낮게 유지하려고 하고 공간이 제한되어 있다는 가정하에(따라서 Raspberry Pi를 선택함) 제가 생각할 수 있는 것은 실제로 없습니다. Pi(모든 반복 버전)는 해당 가격대의 컴퓨터 비전 작업에 매우 고유하게 적합한 SoC를 사용합니다. 좀 더 크고 좀 더 비싼 제품을 사용하고 싶다면 NVIDIA Jetson 보드를 제안할 수 있습니다. (그들은 192개의 CUDA 코어가 통합된 Quadro와 동등한 GPU가 있는 Tegra SoC를 사용하므로 처리를 실행할 수 있습니다. 작업 부하가 훨씬 더 빠르지만 Buildroot를 작동시키는 것은 Pi보다 훨씬 더 복잡합니다.
의견에 대한 응답으로 수정:
프로세스 수준의 병렬화는 멀티스레딩과 동일하지 않으며 크게 다릅니다(가장 큰 차이점은 리소스가 공유되는 방식에 있으며 기본적으로 스레드는 모든 것을 공유하고 프로세스는 아무것도 공유하지 않습니다). 일반적으로 많은 처리가 필요한 경우 스레드 안전에 대해 걱정할 필요 없이 효율적인 코드를 작성하는 것이 더 쉽기 때문에 프로세스 기반 병렬화를 사용하는 것이 (보통) 더 좋습니다.
옵션에 관해서는 언급한 두 가지가 시스템 성능에 큰 영향을 미칠 수 있지만 둘 다 결국 처리량과 대기 시간 간의 균형을 이루게 됩니다. 선점 모델은 커널 모드에서 실행되는 것(예: syscall)을 다시 예약할 수 있는 방법을 제어합니다. 세 가지 옵션이 있습니다:
- 선점 없음: 이는 커널 모드에서 실행 중인 모든 항목이 중단될 수 없음을 의미합니다. 이는 SVR4 및 4.4BSD의 작동 방식과 대부분의 다른 이전 UNIX 시스템 작동 방식과 일치합니다. 처리량 측면에서는 매우 좋지만 대기 시간 측면에서는 매우 나쁩니다. 따라서 일반적으로 CPU가 많은 대형 서버에서만 사용됩니다(CPU가 많다는 것은 선점할 수 있는 작업을 실행할 가능성이 더 높다는 것을 의미합니다).
- 자발적 선점: 이를 통해 커널의 각 기능이 다시 예약될 수 있는 위치를 정의할 수 있습니다. 이는 처리량과 대기 시간 간의 적절한 균형을 제공하므로 대부분의 데스크톱 대상 Linux 배포판에서 사용하는 설정입니다.
- 완전 선점: 이는 커널의 (거의) 모든 코드가 (거의) 언제든지 중단될 수 있음을 의미합니다. 이는 실시간 멀티미디어 작업에 사용되는 시스템과 같이 입력 및 외부 이벤트와 관련하여 매우 낮은 대기 시간이 필요한 시스템에 유용합니다. 처리량 측면에서는 정말 끔찍하지만 대기 시간을 이길 수는 없습니다.
대조적으로 타이머 주파수는 설명하기가 훨씬 쉽습니다. 실행 대기 중인 다른 항목이 있는 경우 해당 항목이 중단 없이 실행될 수 있는 가장 긴 시간을 제어합니다. 값이 높을수록 기간이 짧아지고(대기 시간이 짧아지고 처리량이 낮아짐), 값이 낮을수록 기간이 길어집니다(대기 시간이 길어지고 처리량은 높아집니다). 일반적인 시작의 경우 선점 모델을 자발적으로 설정하고 타이머 주파수를 300Hz로 설정한 다음 먼저 타이머 주파수를 변경하는 실험을 시작하는 것이 좋습니다(일반적으로 눈에 띄는 영향이 더 크므로).
Movidius NCS의 경우 USB 연결로 인해 대역폭이 제한되므로 작업해야 하는 데이터의 양에 따라 가치가 있는지 여부가 달라집니다. Pi에는 USB 2.0 컨트롤러가 하나만 있으므로 다음으로 제한되지 않습니다. Movidius가 설계된 대역폭의 1/10 미만이므로 최소한 이더넷 어댑터와 버스를 공유해야 하므로 대기 시간과 처리량이 모두 저하됩니다. 낮은 속도에서 32비트 색상으로 1920x1080의 단일 프레임만 수행하는 경우 실행 가능할 수 있지만 동일한 비디오를 전체 프레임 속도로 스트리밍 처리해야 하는 경우 대기 시간이 발생할 수 있습니다. 문제. 하나를 사용하기로 선택한 경우 전원이 공급되는 허브를 구입해야 합니다(그렇지 않으면 Pi가 제공할 수 있는 것보다 더 많은 전력을 끌어오려고 시도하는 데 문제가 있을 수 있습니다).