메모리 문제 해결하기

TL;DR

Web에서 웹캠 프레임 데이터를 받아 처리를 하는 프로그램을 만들고 있다. 근데 시간이 지나면 메모리 문제가 발생한다. 이를 해결하기 위해 여러 방법을 시도하게 되는데!

현재 상황과 문제점

프로세스는 웹캠 프레임 데이터를 처리하기 위해 큐를 활용하고 있다. 그러나 매번 새로운 데이터가 추가될 때마다 메모리를 할당하고 큐에 저장한 후, 데이터를 처리한 후에 해당 데이터의 메모리를 해제해야 하는 과정에서 문제가 발생하고 있다. 이 과정에서 메모리 할당이 해제보다 빠르게 진행되어 메모리 사용량이 지나치게 늘어나는 현상이 나타나고 있다. 이로 인해 큐에 저장되는 데이터의 양이 해제되는 데이터의 양보다 많아지는 문제가 발생하고 있다.

alt text

현재 설정된 최대 메모리 한계는 약 2GB이지만, 실제 데이터가 저장되는 양은 이를 초과한다. 예를 들어, 각 데이터가 800(width) * 600(height) * 4(rgba) 바이트 크기로 저장되고 이러한 데이터가 1729개 저장된다면 전체 메모리 사용량은 3.18GB에 이를 것이다.

해결 방안

이 문제를 해결하기 위해 매번 새로운 데이터가 추가될 때마다 메모리를 할당하는 것보다는 미리 몇 개의 메모리를 할당하고 재사용하는 방법을 추천 받았다. 이를 위해 원형 큐를 변형하여 사용하기로 결정했다.

원형 큐를 선택한 이유는 미리 몇 개의 메모리를 할당하여 재사용할 수 있으며, 큐가 가득 찬 경우 가장 오래된 데이터를 제거하고 새로운 데이터를 추가할 수 있기 때문이다. 이러한 방식은 웹캠 프레임 데이터의 손실이 허용되었기 때문에 가능했다.

해야할 조치는 다음과 같다.

  1. 매번 데이터를 저장할 때마다 새로운 메모리를 할당하는 것이 아니라, 몇 개의 메모리를 미리 할당하여 재사용해야한다.
  2. 큐에 데이터가 가득 찬 경우, 가장 오래된 데이터를 제거하고 새로운 데이터를 추가해야 한다. 이를 통해 큐의 크기를 일정하게 유지할 수 있다.

원형 큐 활용하기

위의 문제를 해결하기 위해 원형 큐를 활용하여 구현했다.

원형 큐의 생성

1
2
3
4
5
CircularQueue(unsigned int buffer_size) : head_(0), tail_(0), size_(0), buffer_size_(buffer_size) {
for (int i = 0; i < MAX_BUFFER_SIZE; i++) {
data_.push_back(new unsigned char[buffer_size]);
}
}

원하는 버퍼 사이즈만큼 추가하여 원형 큐를 생성한다. 이때, head는 나중에 데이터를 가져올 때의 인덱스를, tail은 데이터를 추가할 위치를 나타낸다.

데이터 추가

1
2
3
4
5
6
7
8
9
10
void Enqueue(unsigned char* value) {
if (size_ < MAX_BUFFER_SIZE) {
size_++;
} else {
head_ = (head_ + 1) % MAX_BUFFER_SIZE;
}

std::memcpy(data_[tail_], value, buffer_size_);
tail_ = (tail_ + 1) % MAX_BUFFER_SIZE;
}

데이터를 추가할 때, 큐의 크기를 넘어서는 경우 가장 오래된 데이터를 제거한 후 새로운 데이터를 추가한다.

원형 큐의 특성상 가장 오래된 데이터의 위치는 head에 저장되어 있다. 따라서 head를 한칸씩 증가시킴으로써 가장 오래된 데이터를 제거하고 새로운 데이터를 추가할 공간을 만들어 준다.

새로운 데이터를 큐의 tail 위치에 복사합니다. 이때 std::memcpy 함수를 사용하여 새로운 데이터를 큐에 복사합니다. 그리고 tail_을 한 칸 증가시켜 다음 데이터가 저장될 위치를 나타낸다.

이렇게 함으로써 큐의 크기를 넘어가는 경우 가장 오래된 데이터를 제거하고 새로운 데이터를 추가할 수 있게된다.

데이터 가져오기

1
2
3
4
5
6
7
8
9
unsigned char* Dequeue() {
if (size_ == 0) {
return nullptr;
}
unsigned char* value = data_[head_];
head_ = (head_ + 1) % MAX_BUFFER_SIZE;
size_--;
return value;
}

가장 오래된 데이터를 큐에서 가져온다.

큐가 비어있지 않는 경우 큐에서 가장 오래된 데이터를 가져와야한다. 이를 위해 head가 가리키는 위치에 있는 데이터를 가져와 value에 저장한다. 그리고 head를 한 칸 증가시켜 다음으로 가져올 데이터의 위치를 나타낸다.

마지막으로, 큐의 크기를 하나 줄여주고 가져온 데이터를 반환한다.

이를 통해 데이터가 큐의 크기를 넘어가게 되면 가장 오래된 데이터를 지우고 순차적으로 데이터를 배치할 수 있다.

Finish

이러한 데이터를 처리하는 작업은 하지 않아서 메모리 문제가 발생했을 때 당황했었다. 다행히 선배들이 방법을 알려줘서 쉽게 해결할 수 있었다. 무지 무지 감사합니다~

덕분에 메모리가 터치지 않고, 웹에서 웹캠에 새그멘테이션을 적용하고 있다. 좀 더 최적화 해야하고 API도 다듬도 할게 많다!