2016-12-16 2 views
1

필자는 SDR 장치 (초당 1000 만개의 복잡한 샘플 (샘플은 짧은 유형 임))에서 버퍼를 고속으로 가져 오는 도구를 작성했습니다. 그러나 필자가 작성한 코드로 작성된 내용을 다시 볼 때마다 작은 덩어리가 누락되었습니다.C++의 고속 버퍼링

이 문제를 줄이기 위해 시도한 방법은 같은 크기의 두 버퍼를 사용하여 샘플을 놓치지 않기 위해 두 버퍼를 교환하는 것입니다. 청크는 내가 버퍼를 교환하고 샘플을 백 버퍼 (샘플 속도의 3 배)로 오프로드하는 과정을 거칠 때마다 사라지고 필요한 경우 디스크에 새 데이터를 쓰는 새 스레드를 호출합니다.

SDR 장치 자체는 자체 내부 버퍼 크기를 2016과 같은 이상한 것으로 알리고 실제 및 가상 샘플 배열에 대한 두 개의 포인터를 제공합니다. 분명히이 샘플 속도에서 그러한 작은 배열의 오버 헤드를 피하기 위해 크기가 더 큰 스와핑 버퍼를 구현하여 이러한 문제를 피할 수있는 희망으로 65536을 말하지만 아무 소용이 없습니다.

나는 스왑 핑 버퍼의 크기를 줄이면 누락 된 청크가 더 자주 발생하기 때문에 문제가 콜백 함수에서 발생할 가능성이 가장 높다고 지적했다.

잘못된 방식으로 가고 있습니까? 아니면 내 솔루션에 누락 된 부분이 있습니까? 아니면 제대로 작성하지 않았습니까?

나는이 표준 데이터 속도 때문에 느리기 때문에 가능한 표준 libary를 피했다. 따라서 memmove와 memcpy가 필요하다. 유일한 예외는 버퍼 포인터 스와핑 및 스레드 생성입니다. IQType가

IQType<short>* bufferA; 
    IQType<short>* bufferB; 

:

void MiricsDataSource::newSamplesCallBack(short *xi, short *xq, unsigned int firstSampleNum, int grChanged, int rfChanged, int fsChanged, unsigned int numSamples, unsigned int reset, void *cbContext) { 

    MiricsDataSource* mirCtx = static_cast<MiricsDataSource*>(cbContext); 

    for (int i = 0; i < numSamples; ++i) 
    { 
     mirCtx->bufferA[mirCtx->bufferCount] = IQType<short>(xi[i],xq[i]); 
     mirCtx->bufferCount++; 
     if(mirCtx->bufferCount == mirCtx->bufferSize-1) { 
      std::swap(mirCtx->bufferA,mirCtx->bufferB); 
      mirCtx->owner->backBuffer->write(mirCtx->bufferB,mirCtx->bufferSize); 
      mirCtx->bufferCount = 0; 
     } 
    } 
} 

후방 버퍼에 기록하고 관련 t_write 다음 SDR 샘플 데이터를 언로드

template <class T> class IQType { 
public: 
     T inPhaseValue; 
     T quadraturePhaseValue; 

     IQType() : inPhaseValue(0), quadraturePhaseValue(0){}; 
     IQType(T i, T q) : inPhaseValue(i), quadraturePhaseValue(q){}; 
}; 

SDR 장치 콜백 함수로 버퍼를 스와핑

는 구현 기능 :

void BackBuffer::write(const IQType<short>* buff, size_t bLength) { 
    std::thread dumpThread(&BackBuffer::t_write,this,buff,bLength); 
    dumpThread.detach(); 
} 

void BackBuffer::t_write(const IQType<short>* buff, size_t bLength) { 
    std::lock_guard<std::mutex> lck (bufferMutex); 
    memmove(&backBuffer[0],(&backBuffer[0])+bLength,(sizeof(IQType<short>*)*(length-bLength))); 
    memcpy(&backBuffer[length-bLength],buff,(sizeof(IQType<short>*)*(bLength))); 
    if(dumpToFile) { 
     IQType<short>* toWrite = new IQType<short>[bLength]; 
     memcpy(toWrite,buff,(sizeof(IQType<short>*)*(bLength))); 
     strmDmpMgr->write(toWrite,bLength); 
    } 
} 
+0

'표준 라이브러리를 최대한 피했다. 왜냐하면 이런 종류의 데이터 속도에는 너무 느리기 때문에 memmove와 memcpy가 필요하다. 표준 라이브러리 만 보았을 때 나는 그것을 사지 않는다. memcpy/memmove 당신의 경우처럼 유형이 사소한 경우. 정말로 측정하지 않았다면 그 진술을 삭제하십시오. – Arunmu

+0

"나는 표준 라이브러리를 최대한 피했다 [...] 따라서 표준 라이브러리의 기능에 대한 필요성은 조금 모순 된 것처럼 보인다. – user2079303

+0

원래는 std :: rotate를 사용하여 backbuffer를 이동 시켰지만, 30 밀리언 샘플을 저장하기 위해 5 초가 걸렸습니다. memmove는이 작업을 수백 배 빠르게 수행합니다. Justy는 내가 기록하는 것 이외에 backbuffer에있는 데이터로 다른 일을 명확히하기 위해 설명합니다. 후방 버퍼는 기록 된 데이터의 마지막 3 초를 관찰하는데도 사용됩니다. 대기열과 비슷하게 처리되지만 백 버퍼의 크기까지 모든 위치와 길이를 볼 수있는 옵션이 있습니다. – Gelion

답변

1
  1. 가장 큰 비용은 BackBuffer::write에 스레드를 산란한다. 이 작업을 수행하지 마십시오. 하나의 지속적인 백그라운드 스레드를 실행하고 메시지를 보냅니다.

  2. 출력 버퍼가 손상 될 수 있습니다 (첫 번째 버퍼로 스레드가 완료되기 전에 두 번째 버퍼를 채우면 첫 번째 버퍼를 다시 덮어 쓸 수 있습니다).전체 버퍼의 대기열과 빈 버퍼의 대기열을 사용하여 임의의 수의 버퍼를 처리하여 스레드간에 순환시킬 수 있습니다.

    임계 루프에서 동적 할당을 유지하기 위해 최소한의 자유 수준 아래로 떨어지면 백그라운드 버퍼가 새 버퍼 생성을 담당하게 만듭니다.

  3. Voo가 말했듯이 큰 버퍼에 직접 읽는 것만으로 (중간에 memcpy 등을 피하는 것) 더 간단합니다. 그것은리스트 - 오브 - 버퍼 방식보다 덜 탄력적이지만, 여기서 유연성이 필요하다는 것은 분명하지 않습니다.

(예. 한 번만 올바른 값을 저장할 버퍼가 간접을 통해 모든 반복 횟수 증가하지 않는다)하지만 스레드가 주요 문제가 몇 가지 작은 최적화가 있습니다.

1

하나의 가능한 소스는 "데이터 덤프"마다 새 스레드를 만드는 것입니다. 버퍼 크기에 따라 초당 수천 개의 스레드를 생성 할 수 있으므로 프로그램뿐만 아니라 전체 컴퓨터에 심각한 성능 저하가 발생할 수 있습니다. 단일 쓰레드를 만드는 것은 값 비싼 작업입니다. 운영상의 모든 쓰레드들 사이에서 순환해야합니다. + 시스템의 다른 모든 쓰레드를 순환시켜야합니다.

대신 다른 디자인을 제안합니다. 여기에는 버퍼를 덤프하도록 요청하는 이미 실행중인 스레드 풀 (c++ thread pools 검색)이 있습니다. 그런 다음 버퍼의 원형 링을 가질 수 있습니다. 각 버퍼에는 현재 쓰는 위치와 하나의 버퍼가 있습니다.

+0

방금 ​​사용하는 스레드 수를 확인한 결과 약 15 개가 남아 있습니다 (창에 따라). 얼마나 많은 양의 산란을 볼 수있는 더 정확한 방법이 있습니까? – Gelion