거대한 배열을 채우지 않고 큰 문제를 일으키는 전문적인 방법 : 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.