C ++에서 스레드 간 빠른 메시지 전달을위한 메모리 관리


9

비동기 적으로 데이터 메시지를 전송하여 통신하는 두 개의 스레드가 있다고 가정하십시오. 각 스레드에는 일종의 메시지 큐가 있습니다.

내 질문은 매우 낮은 수준입니다. 메모리를 관리하는 가장 효율적인 방법은 무엇입니까? 몇 가지 솔루션을 생각할 수 있습니다.

  1. 발신자는을 통해 개체를 만듭니다 new. 수신자 전화 delete.
  2. 메모리 풀링 (메모리를 보낸 사람에게 다시 전송하기 위해)
  3. 가비지 콜렉션 (예 : Boehm GC)
  4. (객체가 충분히 작은 경우) 힙 할당을 완전히 피하기 위해 값으로 복사

1) 가장 확실한 솔루션이므로 프로토 타입에 사용하겠습니다. 이미 충분할 가능성이 있습니다. 그러나 내 특정 문제와 관계없이 성능 최적화를 위해 어떤 기술이 가장 유망한지 궁금합니다.

특히 스레드 간 정보 흐름에 대한 추가 지식을 사용할 수 있기 때문에 풀링이 이론적으로 최고라고 기대합니다. 그러나 나는 그것이 옳은 것이 가장 어렵다는 것을 두려워합니다. 많은 튜닝 ... :-(

가비지 콜렉션은 나중에 (솔루션 1 이후) 추가하기가 매우 쉬워야하며 성능이 매우 뛰어날 것으로 기대합니다. 따라서 1) 비효율적 인 것으로 판명되면 가장 실용적인 해결책이라고 생각합니다.

객체가 작고 단순하면 값으로 복사하는 것이 가장 빠를 수 있습니다. 그러나 지원되는 메시지를 구현할 때 불필요한 제한이 발생한다고 두려워하므로 피하고 싶습니다.

답변:


9

객체가 작고 단순하면 값으로 복사하는 것이 가장 빠를 수 있습니다. 그러나 지원되는 메시지를 구현할 때 불필요한 제한이 발생한다고 두려워하므로 피하고 싶습니다.

상한을 예상 char buf[256]할 수있는 경우 (예 : 드문 경우에 힙 할당 만 호출 할 수없는 경우)

struct Message
{
    // Stores the message data.
    char buf[256];

    // Points to 'buf' if it fits, heap otherwise.
    char* data;
};

3

큐를 구현하는 방법에 따라 다릅니다.

배열 (라운드 로빈 스타일)을 사용하는 경우 솔루션 4의 상한 크기를 설정해야합니다. 링크 된 대기열을 사용하는 경우 할당 된 객체가 필요합니다.

그런 다음, 리소스 풀링은 그냥 새로운 교체와 함께 삭제하면 쉽게 수행 할 수 있습니다 AllocMessage<T>freeMessage<T>. 내 제안은 T콘크리트를 할당 할 때 잠재적 인 크기의 양을 제한 하고 반올림하는 것 messages입니다.

똑바로 가비지 수집이 작동 할 수 있지만 많은 부분을 수집해야 할 때 일시 중지가 발생할 수 있으며 새 / 삭제보다 약간 더 성능이 떨어집니다.


3

C ++에있는 경우 스마트 포인터 중 하나를 사용하십시오. unique_ptr 은 아무도 처리 할 수 ​​없을 때까지 기본 객체를 삭제하지 않기 때문에 잘 작동합니다. ptr 오브젝트를 값으로 수신자에게 전달하고 수신자가 오브젝트를 수신하지 않는 경우이를 삭제해야하는 스레드에 대해 걱정할 필요가 없습니다.

여전히 스레드 간 잠금을 처리해야하지만 메모리가 복사되지 않기 때문에 성능이 좋을 것입니다 (ptr 객체 자체 만 작음).

힙에 메모리를 할당하는 것이 가장 빠르지 않으므로 풀링을 사용하여 훨씬 빠르게 수행 할 수 있습니다. 풀의 사전 크기 힙에서 다음 블록을 가져 오면 이를 위해 기존 라이브러리 를 사용하십시오.


2
잠금은 일반적으로 메모리 복사보다 훨씬 큰 문제입니다. 그냥 말하면
tdammers

당신이 쓸 때 unique_ptr, 나는 당신이 의미하는 것 같아요 shared_ptr. 그러나 스마트 포인터를 사용하는 것이 리소스 관리에 도움이된다는 것은 의심의 여지가 없지만, 어떤 형태의 메모리 할당 및 할당 해제를 사용하고 있다는 사실은 변경하지 않습니다. 나는이 질문이 더 낮은 수준이라고 생각합니다.
5gon12eder

3

한 스레드에서 다른 스레드로 오브젝트를 통신 할 때 가장 큰 성능 저하는 잠금을 잡는 오버 헤드입니다. 이것은 몇 마이크로 초 정도이며, 이는 한 쌍의 new/ delete소요 시간 (100 나노초 정도) 보다 훨씬 많은 시간 입니다. Sane new구현은 성능 저하를 피하기 위해 거의 모든 비용으로 잠금을 피하려고합니다.

즉, 한 스레드에서 다른 스레드로 오브젝트를 통신 할 때 잠금을 잡을 필요가 없도록하고 싶습니다. 이것을 달성하는 두 가지 일반적인 방법을 알고 있습니다. 둘 다 한 명의 발신자와 한 명의 수신자간에 단방향으로 만 작동합니다.

  1. 링 버퍼를 사용하십시오. 두 프로세스 모두이 버퍼에 대한 하나의 포인터를 제어합니다. 하나는 읽기 포인터이고 다른 하나는 쓰기 포인터입니다.

    • 발신자는 먼저 포인터를 비교하여 요소를 추가 할 공간이 있는지 확인한 다음 요소를 추가 한 다음 쓰기 포인터를 증가시킵니다.

    • 수신기는 포인터를 비교하여 읽을 요소가 있는지 확인한 다음 요소를 읽은 다음 읽기 포인터를 증가시킵니다.

    포인터는 스레드간에 공유되므로 원자 적이어야합니다. 그러나 각 포인터는 한 스레드 만 수정하고 다른 포인터는 포인터에 대한 읽기 권한 만 필요합니다. 버퍼의 요소는 포인터 자체 일 수 있으므로 발신자 블록을 만들지 않는 크기로 링 버퍼의 크기를 쉽게 조정할 수 있습니다.

  2. 항상 하나 이상의 요소가 포함 된 연결 목록을 사용하십시오. 수신자에게는 첫 번째 요소에 대한 포인터가 있고, 발신자에게는 마지막 요소에 대한 포인터가 있습니다. 이 포인터는 공유되지 않습니다.

    • 발신자는 연결된 목록에 대한 새 노드를 작성하고 next포인터를로 설정 합니다 nullptr. 그런 다음 next마지막 요소 의 포인터를 업데이트 하여 새 요소를 가리 킵니다. 마지막으로 새로운 요소를 자체 포인터에 저장합니다.

    • 수신기 next는 사용 가능한 새 데이터가 있는지 확인하기 위해 첫 번째 요소 의 포인터를 감시합니다 . 그렇다면 이전 첫 번째 요소를 삭제하고 현재 요소를 가리 키도록 자체 포인터를 이동하여 처리를 시작합니다.

    이 설정에서 next포인터는 원자 적이어야하며 발신자는 next포인터 를 설정 한 후 마지막 두 번째 요소를 역 참조하지 않아야합니다 . 물론 발신자가 차단할 필요가 없다는 장점이 있습니다.

두 가지 접근 방식은 모든 잠금 기반 접근 방식보다 훨씬 빠르지 만 제대로 작동하려면 신중하게 구현해야합니다. 물론 포인터 쓰기 /로드의 기본 하드웨어 원 자성이 필요합니다. 귀하의 경우 atomic<>구현이 내부적으로 잠금 장치를 사용하여, 당신은 거의 운명을 정한다.

마찬가지로, 독자와 작가가 여러 명인 경우 거의 운명이 될 것입니다. 잠금없는 체계를 생각해 낼 수는 있지만 최선을 다하기 란 쉽지 않습니다. 이러한 상황은 자물쇠로 다루기가 훨씬 쉽습니다. 당신이 잠금을 잡아 그러나, 일단, 당신에 대해 걱정 중지 할 수 있습니다 new/ delete성능을 제공합니다.


+1 CAS 루프를 사용하는 동시 큐에 대한 대안으로이 링 버퍼 솔루션을 확인해야합니다. 매우 유망한 소리.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.