거대한 배열을 채우지 않고 큰 문제를 일으키는 전문적인 방법 : C ++, 배열의 일부에서 사용 가능한 메모리


20

물리 시뮬레이션을 개발 중이며 프로그래밍에 익숙하지 않아서 큰 프로그램 (주로 메모리 문제)을 생성 할 때 계속 문제가 발생합니다. 동적 메모리 할당 및 삭제 (신규 / 삭제 등)에 대해 알고 있지만 프로그램을 구성하는 방법에 대한 더 나은 접근 방식이 필요합니다.

매우 빠른 샘플링 속도로 며칠 동안 실행되는 실험을 시뮬레이션한다고 가정 해 봅시다. 10 억 개의 샘플을 시뮬레이트하고 실행해야합니다.

단순화 된 버전으로서, 우리는 프로그램이 전압 V [i]를 취하고 5를 합산한다고 말합니다 :

즉 NewV [0] = V [0] + V [1] + V [2] + V [3] + V [4]

NewV [1] = V [1] + V [2] + V [3] + V [4] + V [5]

NewV [2] = V [2] + V [3] + V [4] + V [5] + V [6] ... 이는 10 억 개의 샘플에 대해 계속됩니다.

결국, 나는 V [0], V [1], ..., V [1000000000]을 가지게되는데, 대신 다음 단계를 위해 저장해야 할 유일한 것이 마지막 5V [i] 일 때 에스.

메모리를 다시 자유롭게 사용할 수 있도록 배열의 일부 를 삭제 / 할당 해제하려면 어떻게해야합니까 (예를 들어 더 이상 필요하지 않은 예제의 첫 부분 다음에 V [0])? 그러한 프로그램을 구성하는 방법에 대한 대안이 있습니까?

malloc / free에 대해 들었지만 C ++에서 사용해서는 안되며 더 나은 대안이 있다고 들었습니다.

매우 감사합니다!

tldr; 배열 (개별 요소)의 일부로 무엇을해야합니까? 더 많은 메모리를 차지하는 더 이상 필요하지 않습니까?


2
배열의 일부를 할당 해제 할 수 없습니다. 다른 곳에서 더 작은 배열로 다시 할당 할 수는 있지만 비용이 많이들 수 있습니다. 대신 링크 된 목록과 같은 다른 데이터 구조를 사용할 수 있습니다. 아마도 V새로운 배열 대신 단계를 저장할 수도 있습니다 . 그러나 기본적으로 귀하의 문제는 알고리즘 또는 데이터 구조에 있다고 생각되며 세부 정보가 없으므로 효율적으로 수행하는 방법을 알기가 어렵습니다.
Vincent Savard

4
참고 : 임의의 길이의 SMA는이 반복 관계를 사용하여 특히 빠르게 계산할 수 있습니다. NewV [n] = NewV [n-1]-V [n-1] + V [n + 4] (표시). 그러나이 필터는 특히 유용한 필터는 아닙니다. 그들의 주파수 응답은 sinc이며, 당신이 원하는 것 (실제로 높은 사이드 로브)은 아닙니다.
Steve Cox

2
SMA = 궁금한 사람은 단순한 이동 평균.
Charles

3
@SteveCox, 그가 쓴 방식으로, 그는 FIR 필터를 가지고 있습니다. 재발은 동등한 IIR 양식입니다. 어느 쪽이든, 마지막 N 판독 값의 순환 버퍼를 유지하게됩니다.
John R. Strohm

@ JohnR.Strohm 임펄스 응답은 동일하고 유한합니다
Steve Cox

답변:


58

"매끄럽게 5"로 설명하는 것은 유한 임펄스 응답 (FIR) 디지털 필터입니다. 이러한 필터는 순환 버퍼로 구현됩니다. 마지막 N 값만 유지하고, 가장 오래된 값이 어디에 있는지 알려주는 인덱스를 버퍼에 유지하고, 각 단계에서 가장 오래된 값으로 현재 가장 오래된 값을 겹쳐 쓰며 매번 인덱스를 단계별로 실행합니다.

수집 한 데이터를 크런치 할 디스크에 보관합니다.

환경에 따라 경험이 풍부한 도움을받는 것이 더 좋은 곳 중 하나 일 수 있습니다. 대학에서는 컴퓨터 과학 부서의 게시판에 메모를 작성하여 몇 시간 동안 작업 한 학생 임금 (또는 학생 상담 비율)을 제공하여 데이터를 처리하는 데 도움을줍니다. 또는 학부 연구 기회 포인트를 제공 할 수도 있습니다. 또는 뭔가.


6
실제로 원형 버퍼는 내가 찾고있는 것 같습니다! 이제 boost C ++ 라이브러리를 설치하고 boost / circular_buffer.hpp를 포함 시켰으며 예상대로 작동합니다. 감사합니다, @ 존
Drummermean

2
매우 짧은 FIR 필터 만 소프트웨어에서 직접 형식으로 구현되며 SMA는 거의 없습니다.
Steve Cox

@SteveCox : 사용하는 창 가장자리 수식은 정수 및 고정 소수점 필터에 매우 효과적이지만 연산이 정류되지 않는 부동 소수점에 대해서는 올바르지 않습니다.
Ben Voigt

@ BenVoigt 나는 당신이 내 다른 의견에 응답하려고한다고 생각하지만, 그 형태는 양자화 주위에 한계주기를 도입하여 매우 까다로울 수 있습니다. 고맙게도이 특정 제한주기는 안정적입니다.
Steve Cox

실제로 사용하기 위해 순환 버퍼를 향상시킬 필요는 없습니다. uu 필요한 것보다 훨씬 많은 메모리를 사용하게됩니다.
GameDeveloper

13

추가적인 수준의 간접 지시를 추가하여 모든 문제를 해결할 수 있습니다. 그렇게하세요.

C ++에서는 배열의 일부를 삭제할 수 없습니다. 그러나 유지하려는 데이터 만 보유한 새 배열을 만든 다음 이전 배열을 삭제할 수 있습니다. 따라서 원하지 않는 요소를 정면에서 "제거"할 수있는 데이터 구조를 구축 할 수 있습니다. 실제로 할 일은 새 배열을 만들고 제거되지 않은 요소를 새 배열로 복사 한 다음 이전 배열을 삭제하는 것입니다.

또는 std::deque이미 사용할 수있는을 사용할 수 있습니다. deque"양쪽 대기열"은 한 쪽 끝에서 요소를 삭제하고 다른 쪽 끝에서 요소를 추가하는 경우를위한 데이터 구조입니다.


30
모든 수준의 간접 지향을 제외하고 추가적인 수준의 간접 지정을 추가하면 모든 문제를 해결할 수 있습니다 .
YSC

17
@YSC : and spelling :)
Monica와의

1
이 특별한 문제 std::deque는 갈 길입니다
davidbak

7
@davidbak-무엇? 지속적으로 메모리를 할당하고 해제 할 필요가 없습니다. 초기화시 한 번 할당 된 고정 크기 원형 버퍼가이 문제에 훨씬 더 적합합니다.
David Hammen

2
@DavidHammen : 아마도 1) 표준 라이브러리에는 툴킷에 "고정 크기 원형 버퍼"가 없습니다. 2) 실제로 이러한 최적화가 필요한 경우을 통해 재 할당을 최소화하기 위해 할당 자 작업을 수행 할 수 있습니다 deque. 즉, 요청에 따라 할당을 저장하고 재사용합니다. 따라서 deque문제에 대한 완벽한 해결책으로 보입니다.
Nicol Bolas

4

귀하가받은 FIR 및 SMA 답변은 귀하의 경우에 적합하지만보다 일반적인 접근 방식을 추진할 기회를 갖고 싶습니다.

여기에있는 것은 데이터 스트림 입니다. 모든 데이터를 한 번에 메모리에로드 해야하는 3 가지 큰 단계 (데이터 가져 오기, 계산, 출력 결과)로 프로그램을 구성하는 대신 파이프 라인 으로 구성 할 수 있습니다 .

파이프 라인은 스트림으로 시작하여 변환 한 후 싱크로 푸시합니다.

귀하의 경우 파이프 라인은 다음과 같습니다.

  1. 디스크에서 항목을 읽고 한 번에 하나씩 항목을 내 보냅니다.
  2. 한 번에 하나씩 항목을 받으십시오.받은 각 항목에 대해 마지막으로받은 5 개의 항목 (라운드 버퍼가 들어오는 위치)을 방출하십시오.
  3. 각 그룹마다 결과를 계산하기 위해 한 번에 5 개의 항목을받습니다.
  4. 결과를 받아 디스크에 씁니다.

C ++은 스트림보다는 반복자를 사용하는 경향이 있지만 정직한 스트림은 모델링하기가 쉽습니다 ( 스트림과 비슷한 범위에 대한 제안 이 있습니다).

template <typename T>
class Stream {
public:
    virtual boost::optional<T> next() = 0;
    virtual ~Stream() {}
};

class ReaderStream: public Stream<Item> {
public:
    boost::optional<Item> next() override final;

private:
    std::ifstream file;
};

class WindowStream: public Stream<Window> {
public:
    boost::optional<Window> next() override final;

private:
    Window window;
    Stream<Item>& items;
};

class ResultStream: public Stream<Result> {
public:
    boost::optional<Result> next() override final;

private:
    Stream<Window>& windows;
};

파이프 라인은 다음과 같습니다.

ReaderStream itemStream("input.txt");
WindowStream windowStream(itemsStream, 5);
ResultStream resultStream(windowStream);
std::ofstream results("output.txt", std::ios::binary);

while (boost::optional<Result> result = resultStream.next()) {
    results << *result << "\n";
}

스트림은 항상 적용 가능하지는 않지만 (데이터에 무작위로 액세스해야 할 때 작동하지는 않지만), 스트림이 매우 적습니다. 매우 적은 양의 메모리에서 작동하면 CPU 캐시에 모두 유지됩니다.


또 다른 참고 사항 : 문제가 "엄청나게 평행 한"것처럼 보입니다. 큰 파일을 청크로 분할 할 수 있습니다 (5의 창으로 처리하려면 각 경계에 4 개의 공통 요소가 있어야 함을 명심하십시오) 그런 다음 청크를 병렬로 처리하십시오.

CPU가 병목 현상 (I / O가 아님) 인 경우 파일을 대략 같은 양으로 분할 한 후 코어 당 하나의 프로세스를 시작하여 속도를 높일 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.