FIFO에 어떤 STL 컨테이너를 사용해야합니까?


93

내 요구에 가장 적합한 STL 컨테이너는 무엇입니까? 기본적으로 가장 오래된 요소 (약 백만 시간)를 사용 push_back하면서 지속적으로 새로운 요소를 포함하는 10 개의 요소 너비 컨테이너가 pop_front있습니다.

현재 std::deque작업에 a 를 사용하고 있지만 std::list자체를 재 할당 할 필요가 없기 때문에 a 가 더 효율적 인지 궁금 합니다 (또는 a std::deque를 a로 착각하고있을 수도 있습니다 std::vector). 아니면 내 필요에 맞는 더 효율적인 컨테이너가 있습니까?

추신 : 랜덤 액세스가 필요하지 않습니다


5
둘 다 시도해보고 어느 것이 더 빠른지 확인하십시오.
KTC

5
나는 이것을하려고했지만 이론적 인 답도 찾고 있었다.
Gab Royer

2
std::deque재 할당되지 않습니다. a std::list와 a 의 하이브리드로 a std::vector보다 큰 청크를 할당 std::list하지만 std::vector.
Matt Price

2
아니오, 다음은 표준의 관련 보증입니다. "데크의 시작 또는 끝에 단일 요소를 삽입하는 것은 항상 일정한 시간이 걸리며 T의 복사 생성자에 대한 단일 호출을 발생시킵니다."
Matt Price

1
@John : 아니요, 다시 할당합니다. 어쩌면 우리는 용어를 섞는 것일 수도 있습니다. 재 할당이란 이전 할당을 가져와 새 할당에 복사하고 이전 할당을 버리는 것을 의미한다고 생각합니다.
GManNickG

답변:


198

무수히 많은 답변이 있기 때문에 혼란 스러울 수 있지만 요약하면 다음과 같습니다.

를 사용합니다 std::queue. 그 이유는 간단합니다. FIFO 구조이기 때문입니다. FIFO를 원하면 std::queue.

그것은 당신의 의도를 다른 사람에게, 심지어 당신 자신에게도 분명하게합니다. A std::list또는 std::deque하지 않습니다. 목록은 FIFO 구조가 수행 deque할 수 있는 작업이 아닌 임의의 위치에 삽입 및 제거 할 수 있으며, 이는 FIFO 구조가 수행 할 수없는 일이기도합니다.

이것이 당신이 queue.

이제 성능에 대해 물었습니다. 첫째, 항상 다음과 같은 중요한 경험 법칙을 기억하십시오. 좋은 코드는 우선, 성능은 마지막입니다.

그 이유는 간단합니다. 청결과 우아함이 거의 항상 마지막으로 끝나기 전에 성능을 위해 노력하는 사람들입니다. 그들의 코드는 아무 것도 얻기 위해 좋은 것을 모두 버리기 때문에 엉망진창이됩니다.

읽기 쉽고 좋은 코드를 먼저 작성하면 대부분의 성능 문제가 저절로 해결됩니다. 나중에 성능이 부족하다는 사실을 알게되면 이제는 깔끔하고 멋진 코드에 프로파일 러를 쉽게 추가하고 문제가 어디에 있는지 알아낼 수 있습니다.

std::queue, 어댑터 일뿐입니다. 안전한 인터페이스를 제공하지만 내부에는 다른 컨테이너를 사용합니다. 이 기본 컨테이너를 선택할 수 있으며 이는 상당한 유연성을 허용합니다.

그렇다면 어떤 기본 컨테이너를 사용해야합니까? 우리는 알고 std::list그리고 std::deque둘 다 필요한 기능을 제공 ( push_back(), pop_front(), 및 front()), 그래서 우리는 어떻게 결정합니까?

첫째, 메모리 할당 (및 할당 해제)은 일반적으로 OS로 가서 무언가를 요청하기 때문에 빠른 작업이 아니라는 점을 이해하십시오. A list는 무언가가 추가 될 때마다 메모리를 할당하고, 사라질 때 할당을 해제해야합니다.

deque반면 A 는 청크로 할당됩니다. 그것은보다 적게 할당합니다 list. 목록으로 생각하면 각 메모리 청크는 여러 노드를 보유 할 수 있습니다. (물론, 어떻게 작동하는지 정말 배우는 것이 좋습니다 .)

그래서, 그것만으로 deque는 더 잘 수행되어야합니다. 왜냐하면 그것은 자주 기억을 다루지 않기 때문입니다. 일정한 크기의 데이터를 처리한다는 사실과 섞여서 데이터를 처음 통과 한 후에는 할당 할 필요가없는 반면 목록은 지속적으로 할당 및 할당 해제됩니다.

두 번째로 이해해야 할 것은 캐시 성능 입니다. RAM으로 나가는 것은 느리기 때문에 CPU가 정말로 필요할 때 메모리 덩어리를 캐시로 되돌림으로써이 시간을 최대한 활용합니다. deque는 메모리 청크에 할당 되기 때문에이 컨테이너의 요소에 액세스하면 CPU가 나머지 컨테이너도 다시 가져올 가능성이 있습니다. 이제 deque데이터가 캐시에 있기 때문에에 대한 추가 액세스 가 빨라집니다.

이것은 데이터가 한 번에 하나씩 할당되는 목록과 다릅니다. 이는 데이터가 메모리의 모든 위치에 분산 될 수 있으며 캐시 성능이 나빠질 수 있음을 의미합니다.

따라서이를 고려하면 a deque가 더 나은 선택이어야합니다. 이것이 .NET Framework를 사용할 때 기본 컨테이너 인 이유 queue입니다. 즉, 이것은 여전히 ​​(매우) 교육을받은 추측 일뿐입니다.이 코드를 프로파일 링해야합니다 deque. 한 테스트를 사용하고 다른 테스트를 사용하여 list확실히 알 수 있습니다.

하지만 기억하세요 : 깨끗한 인터페이스로 코드를 작업 한 다음 성능에 대해 걱정하세요.

John은 list또는deque 성능 저하의 원인이됩니다. 다시 한 번, 그도 나도 직접 프로파일 링하지 않고 확실히 말할 수는 없지만 컴파일러가 호출을 인라인 할 가능성이 queue있습니다. 즉,라고 말하면 함수 호출을 완전히 건너 뛰고 queue.push()실제로라고 말할 것 queue.container.push_back()입니다.

다시 한 번, 이것은 교육 된 추측 일 뿐이지 만 queue기본 컨테이너를 원시 상태로 사용하는 것과 비교할 때를 사용하면 성능이 저하되지 않습니다. 전에 말했듯이를 사용하십시오 queue. 왜냐하면 깨끗하고 사용하기 쉽고 안전하기 때문이며 실제로 문제 프로필 및 테스트가되는 경우입니다.


10
+1-boost :: circular_buffer <>가 최상의 성능을 발휘하는 것으로 밝혀지면이를 기본 컨테이너로 사용합니다 (필수 push_back (), pop_front (), front () 및 back ()도 제공합니다.) ).
Michael Burr

2
자세히 설명 해주셔서 승인되었습니다 (시간을 내 주셔서 감사합니다). 마지막으로 좋은 코드 첫 번째 성능에 관해서는 이것이 나의 가장 큰 기본값 중 하나임을 인정해야합니다. 저는 항상 첫 번째 실행에서 완벽하게 노력하려고합니다. 먼저 deque를 사용하여 코드를 작성했지만, 그게 아니기 때문에 내가 생각했던 것만 큼 (거의 실시간이되어야한다고 생각하는) 공연을하는데, 조금 개선해야한다고 생각했다. Neil도 말했듯이, 나는 정말로 프로파일 러를 사용 했어야했다 ... 비록 그것이 정말로 중요하지 않지만 지금 이러한 실수를 저질렀다는 것이 행복하지만. 여러분 모두 감사합니다.
Gab Royer

4
-1 문제를 해결하지 못하고 쓸데없는 대답을 부풀려서. 여기서 정답은 짧고 boost :: circular_buffer <>입니다.
Dmitry Chichkov 2014 년

1
"좋은 코드는 우선, 성능은 마지막"은 멋진 인용문입니다. 단지 모든 사람이 :) 이해하는 경우
thegreendroid

프로파일 링에 대한 스트레스에 감사드립니다. 엄지 손가락의 규칙을 제공하는 프로파일 링을 증명 한 후 한 가지와 것은 더 나은 일
talekeDskobeDa

28

확인하십시오 std::queue. 기본 컨테이너 유형을 래핑하고 기본 컨테이너는 std::deque입니다.


3
모든 추가 레이어 컴파일러에 의해 제거됩니다. 당신의 논리에 따라 우리는 모두 어셈블리로 프로그래밍해야합니다. 언어는 방해가되는 쉘일 뿐이 기 때문입니다. 요점은 작업에 올바른 유형을 사용하는 것입니다. 그리고 queue그 유형입니다. 좋은 코드는 먼저, 성능은 나중에. 지옥, 대부분의 성능은 애초에 좋은 코드를 사용하는 데서 나온다.
GManNickG

2
모호하게해서 미안합니다. 제 요점은 큐가 정확히 질문이 요구하는 것이고 C ++ 디자이너는 deque가이 사용 사례에 대한 좋은 기본 컨테이너라고 생각했습니다.
Mark Ransom

2
이 질문에는 성능이 부족하다는 것을 나타내는 것은 없습니다. 많은 초보자는 현재 솔루션이 허용 가능한지 여부에 관계없이 주어진 문제에 대한 가장 효과적인 솔루션에 대해 끊임없이 질문합니다.
jalf 2009-08-11

1
@John, 그가 성능이 부족하다는 것을 알게된다면 queue, 내가 말했듯이 안전 장치의 껍질을 벗기는 것은 성능을 향상시키지 않을 것입니다. list성능이 더 나빠질을 제안 하셨습니다.
GManNickG

3
std :: queue <>에 대한 것은 deque <>가 당신이 원하는 것이 아니라면 (성능이나 어떤 이유로 든), std :: list를 백업 저장소로 사용하도록 변경하는 것이 한 줄로 된 것입니다. GMan이 돌아왔다. 그리고 정말로 목록 대신 링 버퍼를 사용하고 싶다면 boost :: circular_buffer <>가 바로 드롭됩니다. std :: queue <>는 사용해야 할 '인터페이스'가 거의 확실합니다. 이에 대한 백업 저장소는 마음대로 변경할 수 있습니다.
Michael Burr


7

가장 오래된 요소 (약 백만 시간)를 사용 push_back하면서 계속해서 새로운 요소를 pop_front만듭니다.

백만은 실제로 컴퓨팅에서 큰 숫자가 아닙니다. 다른 사람들이 제안했듯이를 std::queue첫 번째 솔루션으로 사용 하십시오. 너무 느린 경우에는 프로파일 러를 사용하여 병목 현상을 식별하고 (추측하지 마십시오!) 동일한 인터페이스를 가진 다른 컨테이너를 사용하여 다시 구현하십시오.


1
글쎄요, 제가하고 싶은 것은 실시간이어야하므로 큰 숫자입니다. 당신이 바로 있지만 나는 ... 원인을 확인하기 위해 프로파일 러를 사용한 것을
갑 이어

문제는 프로파일 러를 사용하는 데 실제로 익숙하지 않다는 것입니다 (우리는 클래스 중 하나에서 gprof를 약간 사용했지만 실제로 깊이 들어 가지 않았습니다 ...). 저에게 리소스를 알려 주시면 대단히 감사하겠습니다! 추신. 내가 VS2008 사용
갑 이어

@Gab : 어떤 VS2008이 있습니까 (Express, Pro ...)? 일부는 프로파일 러와 함께 제공됩니다.
sbi 2009-08-12

죄송합니다 @Gab, 나는 VS 더 이상 그래서 정말 조언을 할 수없는 사용하지 마십시오

@Sbi, 내가보고있는 것은 팀 시스템 에디션 (내가 액세스 할 수있는)에만 있습니다. 나는 이것을 조사 할 것이다.
Gab Royer

5

왜 안돼 std::queue? 그것이 가진 모든 것은 push_backpop_front입니다.


3

큐는 아마보다 간단한 인터페이스 양단 큐 하지만 그런 작은 목록, 성능 차이는 아마도 무시할 수있다.

목록도 마찬가지입니다 . 원하는 API를 선택할 수 있습니다.


그러나 나는 지속적인 push_back이 대기열을 만들고 있는지 아니면 deque를 재 할당하는지 궁금합니다
Gab Royer

std :: queue는 다른 컨테이너를 둘러싼 래퍼이므로 deque를 래핑하는 큐는 raw deque보다 효율성이 떨어집니다.
John Millikin

1
10 개 항목의 경우 성능은 매우 작은 문제가 될 가능성이 높으므로 "효율성"은 코드 타임보다 프로그래머 타임에서 측정하는 것이 더 낫습니다. 그리고 적절한 컴파일러 최적화에 의한 큐에서 deque 로의 호출은 아무것도 아닙니다.
lavinio

2
@John : 이러한 성능 차이를 보여주는 일련의 벤치 마크를 보여주고 싶습니다. 원시 데크보다 덜 효율적입니다. C ++ 컴파일러는 매우 공격적으로 인라인됩니다.
jalf 2009-08-11

3
나는 그것을 시도했다. : VC9의 속도를 위해 릴리스 빌드에서 100,000,000 pop_front () 및 push_back () rand () int 번호가있는 DA quick & dirty 10 요소 컨테이너는 다음을 제공합니다. list (27), queue (6), deque (6), array (8) .
KTC

0

를 사용 std::queue하되 두 표준 Container클래스 의 성능 절충점을 알고 있어야 합니다.

기본적 std::queue으로은 std::deque. 일반적으로 많은 수의 항목을 포함하는 대기열 수가 적을 때 좋은 성능을 제공합니다. 이는 일반적인 경우입니다.

그러나 std :: deque 구현에 눈이 멀지 마십시오 . 구체적으로 특별히:

"... deque는 일반적으로 최소한의 메모리 비용이 큽니다. 하나의 요소 만 포함하는 deque는 전체 내부 배열을 할당해야합니다 (예 : 64 비트 libstdc ++에서 개체 크기의 8 배, 개체 크기의 16 배 또는 4096 바이트 중 더 큰 쪽). , 64 비트 libc ++). "

이를 해결하려면 대기열 항목이 대기열에 넣고 싶은 항목이라고 가정합니다. 즉, 크기가 상당히 작다고 가정하면 각각 30,000 개의 항목을 포함하는 4 개의 대기열 std::deque이있는 경우 구현이 선택 옵션이됩니다. 반대로, 각각 4 개의 항목을 포함하는 30,000 개의 대기열이있는 경우 해당 시나리오 std::list에서 std::deque오버 헤드를 상각하지 않으므로 구현이 최적 일 것 입니다.

특정 조건에서 캐시가 어떻게 왕인지, Stroustrup이 연결 목록을 싫어하는지 등에 대한 많은 의견을 읽을 수 있습니다.이 모든 것이 사실입니다. 맹목적으로 받아들이지 마십시오. 두 번째 시나리오에서는 기본 std::deque구현이 수행 될 가능성이 거의 없습니다 . 사용량을 평가하고 측정하십시오.


-1

이 경우는 직접 작성할 수있을 정도로 간단합니다. 다음은 STL 사용이 너무 많은 공간을 차지하는 마이크로 컨트롤러 상황에서 잘 작동하는 것입니다. 인터럽트 핸들러에서 메인 루프로 데이터와 신호를 전달하는 좋은 방법입니다.

// FIFO with circular buffer
#define fifo_size 4

class Fifo {
  uint8_t buff[fifo_size];
  int writePtr = 0;
  int readPtr = 0;
  
public:  
  void put(uint8_t val) {
    buff[writePtr%fifo_size] = val;
    writePtr++;
  }
  uint8_t get() {
    uint8_t val = NULL;
    if(readPtr < writePtr) {
      val = buff[readPtr%fifo_size];
      readPtr++;
      
      // reset pointers to avoid overflow
      if(readPtr > fifo_size) {
        writePtr = writePtr%fifo_size;
        readPtr = readPtr%fifo_size;
      }
    }
    return val;
  }
  int count() { return (writePtr - readPtr);}
};

하지만 어떻게 / 언제 일어날까요?
user10658782

오, 왠지 그럴 수 있다고 생각했습니다. 신경 쓰지 마!
Ry-
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.