std :: shared_ptr에 상응하는 원자가 아닌 것이 있습니까? 그런데 왜 <memory>에 하나가 없나요?


88

이것은 원자성에 관한 두 부분으로 구성된 질문입니다 std::shared_ptr.

1. 내가 말할 수있는 한, 원 자성 std::shared_ptr에서 유일한 스마트 포인터입니다 <memory>. std::shared_ptr사용 가능한 비 원자 버전이 있는지 궁금합니다 (에서 아무것도 볼 수 없으므로 <memory>Boost의 것과 같이 표준 외부의 제안에도 열려 있습니다). 나는 boost::shared_ptr또한 원자 ( BOOST_SP_DISABLE_THREADS정의되지 않은 경우 ) 라는 것을 알고 있지만 다른 대안이있을 수 있습니까? 와 동일한 의미를 std::shared_ptr갖지만 원 자성이없는 것을 찾고 있습니다.

2. 나는 왜 std::shared_ptr원 자성 이 있는지 이해합니다 . 좀 좋네요. 그러나 모든 상황에 적합하지는 않으며 C ++는 역사적으로 "사용한만큼만 지불"하는 만트라를 가지고 있습니다. 여러 스레드를 사용하지 않거나 여러 스레드를 사용하지만 스레드간에 포인터 소유권을 공유하지 않는 경우 원자 스마트 포인터는 과잉입니다. 두 번째 질문은 std::shared_ptrC ++ 11에서 제공되는 비 원자 버전이 아니 었 습니까? ( 이유 가 있다고 가정 ) (답이 단순히 "비 원자 버전은 고려되지 않았습니다"또는 "아무도 비 원자 버전을 요청한 적이 없음"이면 괜찮습니다!).

질문 # 2에서 누군가가 shared_ptr(Boost 또는 표준위원회에) 의 비 원자 버전을 제안했는지 궁금합니다 ( 원자 버전 을 대체하지 않고 shared_ptr공존하기 위해). 구체적인 이유.


4
여기서 정확히 어떤 "비용"을 염려하십니까? 정수를 원자 적으로 증가시키는 비용? 실제 응용 프로그램에 대한 비용이 실제로 발생합니까? 아니면 너무 일찍 최적화하고 있습니까?
Nicol Bolas 2013

9
@NicolBolas : 다른 무엇보다 호기심이 많습니다. 나는 (현재) 비 원자 공유 포인터를 사용하고 싶은 코드 / 프로젝트가 없습니다. 그러나 나는 Boost가 shared_ptr원 자성으로 인해 상당한 속도 저하를 보인 프로젝트를 (과거에) 가지고 있었고 정의 BOOST_DISABLE_THREADS는 눈에 띄는 차이를 만들었 std::shared_ptr습니다 (그와 같은 비용이 들었 는지 모르겠습니다 boost::shared_ptr).
Cornstalks 2013

13
@Close 투표자 : 질문의 어떤 부분이 건설적이지 않습니까? 두 번째 질문에 대한 구체적인 이유 가 없다면 괜찮습니다 (단순한 "단순히 고려되지 않았습니다"라고해도 충분한 대답이 될 것입니다). 존재 하는 특정한 이유 / 이유 가 있는지 궁금 합니다. 그리고 첫 번째 질문은 확실히 타당한 질문입니다. 질문에 대한 설명이 필요하거나 약간 수정해야하는 경우 알려 주시기 바랍니다. 그러나 나는 그것이 건설적이지 않다는 것을 알지 못합니다.
Cornstalks 2013

10
@Cornstalks 글쎄, 그것은 아마도 사람들이 질문이 아무리 타당하고 적절하거나 관련성이 있더라도 "조기 최적화"라고 쉽게 무시할 수있는 질문에 그렇게 잘 반응하지 않는 것 같습니다. 나는 이것을 비 건설적인 것으로 닫을 이유가 없다고 생각합니다.
Christian Rau 2013

13
(지금은 답을 쓸 수 없으므로 닫혀 있으므로 주석 처리) 프로그램이 여러 스레드를 사용하지 않을 때 GCC를 shared_ptr사용하면 refcount에 원자 연산을 사용하지 않습니다. GCC에 대한 패치 는 gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html 에서 (2)를 참조 하여 멀티 스레드 앱에서도 shared_ptr공유되지 않는 객체 에 대해 비 원자 구현을 사용할 수 있습니다. 스레드. 나는 몇 년 동안 그 패치에 앉아 있었어요하지만 GCC 4.9을 위해 그것을 커밋 마지막으로 고려 중이 야
조나단 Wakely

답변:


105

1. std :: shared_ptr의 비 원자 버전이 있는지 궁금합니다.

표준에서 제공하지 않습니다. "타사"라이브러리에서 제공하는 것이있을 수 있습니다. 실제로 C ++ 11 이전과 Boost 이전에는 모든 사람이 자신을 포함하여 자신의 참조 카운트 스마트 포인터를 작성한 것처럼 보였습니다.

2. 두 번째 질문은 왜 C ++ 11에서 제공되는 std :: shared_ptr의 비 원자 버전이 아니 었습니까?

이 질문은 2010 년 Rapperswil 회의에서 논의되었습니다.이 주제는 스위스의 National Body Comment # 20에 의해 소개되었습니다. 귀하의 질문에 귀하가 제공 한 것을 포함하여 논쟁의 양측에 강력한 논쟁이있었습니다. 그러나 토론이 끝날 무렵 .NET의 동기화되지 않은 (비 원자) 버전을 추가하는 것에 대해 압도적으로 (만장일치가 아님) 투표가 이루어졌습니다 shared_ptr.

포함에 대한 주장 :

  • 동기화되지 않은 shared_ptr로 작성된 코드는 나중에 스레드 코드에서 사용되어 결과적으로 경고없이 문제를 디버그하기 어렵게 만들 수 있습니다.

  • 올린 사람 : 참조 카운팅 트래픽에 "하나의 방법"하나를 갖는 "보편적"shared_ptr의 혜택이 원래의 제안 :

    사용 된 기능에 관계없이 동일한 객체 유형을 가지며 타사 라이브러리를 포함한 라이브러리 간의 상호 운용성을 크게 향상시킵니다.

  • 원자 학의 비용은 0은 아니지만 압도적이지 않습니다. 원자 적 연산을 사용할 필요가없는 이동 구성 및 이동 할당을 사용하면 비용이 완화됩니다. 이러한 작업은 일반적으로 vector<shared_ptr<T>>지우기 및 삽입에 사용됩니다 .

  • 사람들이 실제로 원하는 일이라면 원자가 아닌 참조 카운트 스마트 포인터를 작성하는 것을 금지하는 것은 없습니다.

그날 Rapperswil에서 LWG의 마지막 단어는 다음과 같습니다.

CH 20을 거부합니다. 현재로서는 변경할 합의가 없습니다.


7
와, 완벽합니다. 정보 감사합니다! 그것이 바로 제가 찾고자했던 정보입니다.
Cornstalks

> Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries. 그것은 매우 이상한 추론입니다. 타사 라이브러리는 어쨌든 자체 유형을 제공하므로 std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType> 등의 형식으로 제공하는 것이 왜 중요할까요? 당신은 항상 어쨌든 어떤 라이브러리 반환에 코드를 적용해야합니다
진 마이클 Celerier

라이브러리 특정 유형에 관한 한 사실이지만 표준 유형이 타사 API에 표시되는 많은 위치가 있다는 아이디어도 있습니다. 예를 들어, 내 도서관은 std::shared_ptr<std::string>어딘가에 걸릴 수 있습니다 . 다른 사람의 라이브러리도 해당 유형을 사용하는 경우 호출자는 서로 다른 표현간에 변환하는 불편 함이나 오버 헤드없이 동일한 문자열을 우리 모두에게 전달할 수 있으며 이는 모두에게 작은 승리입니다.
Jack O'Connor

52

Howard 's는 이미 질문에 잘 대답했으며 Nicol은 호환되지 않는 많은 포인터 유형이 아닌 단일 표준 공유 포인터 유형을 갖는 이점에 대해 몇 가지 좋은 점을 지적했습니다.

위원회의 결정에 전적으로 동의하지만 특수한 경우에 비동기식 shared_ptr유형 을 사용하는 것이 약간의 이점이 있다고 생각 하므로 주제를 몇 번 조사했습니다.

여러 스레드를 사용하지 않거나 여러 스레드를 사용하지만 스레드간에 포인터 소유권을 공유하지 않는 경우 원자 스마트 포인터는 과잉입니다.

프로그램이 여러 스레드를 사용하지 않을 때 GCC를 사용하면 shared_ptr은 refcount에 원자 연산을 사용하지 않습니다. 이것은 프로그램이 다중 스레드인지 (GNU / 리눅스에서는 프로그램이에 연결되는지 여부를 감지하여 간단하게 수행되는) 래퍼 함수를 ​​통해 참조 횟수를 업데이트하고 libpthread.so그에 따라 원자 적 또는 비원 자적 작업에 디스패치함으로써 수행됩니다.

몇 년 전에 GCC shared_ptr<T>__shared_ptr<T, _LockPolicy>기본 클래스 측면에서 구현 되었기 때문에 명시 적으로 사용하여 다중 스레드 코드에서도 단일 스레드 잠금 정책으로 기본 클래스를 사용할 수 있음을 깨달았습니다 __shared_ptr<T, __gnu_cxx::_S_single>. 안타깝게도 이는 의도 된 사용 사례가 아니었기 때문에 GCC 4.9 이전에는 최적으로 작동하지 않았고 일부 작업은 여전히 ​​래퍼 함수를 ​​사용했기 때문에 명시 적으로 _S_single정책을 요청 했음에도 불구하고 원자 작업으로 전달되었습니다 . http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html 에서 포인트 (2) 참조자세한 내용과 다중 스레드 앱에서도 비 원자 구현을 사용할 수 있도록 GCC에 대한 패치를 참조하십시오. 나는 몇 년 동안 그 패치에 앉아 있었지만 마침내 GCC 4.9에 적용했습니다. 이렇게하면 이와 같은 별칭 템플릿을 사용하여 스레드로부터 안전하지 않지만 약간 더 빠른 공유 포인터 유형을 정의 할 수 있습니다.

template<typename T>
  using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;

이 유형은 상호 운용이 불가능하며 사용자가 추가로 제공 한 동기화 없이는 스레드간에 개체가 공유되지 않을 std::shared_ptr<T>경우에만 사용하기에 안전 shared_ptr_unsynchronized합니다.

물론 이것은 완전히 이식 할 수 없지만 때로는 괜찮습니다. 올바른 전 처리기 해킹을 사용하면가 shared_ptr_unsynchronized<T>별칭 인 경우 다른 구현에서 코드가 여전히 잘 작동합니다 shared_ptr<T>. GCC에서는 조금 더 빠릅니다.


4.9 이전에 GCC를 사용하는 경우 _Sp_counted_base<_S_single>명시 적 전문화를 자신의 코드 에 추가하여이를 사용할 수 있습니다 (그리고 __shared_ptr<T, _S_single>ODR 위반을 방지하기 위해 전문화를 포함하지 않고 인스턴스화 하지 않도록 보장 ). 이러한 std유형의 특수화를 추가하는 것은 기술적으로 정의되지 않았지만 이 경우 GCC에 전문화를 추가하는 것과 자신의 코드에 추가하는 것 사이에는 차이가 없기 때문입니다.


2
템플릿 별칭의 예에 오타가 있습니까? 즉 shared_ptr_unsynchronized = std :: __ shared_ptr <을 읽어야한다고 생각합니다. 덧붙여서 오늘 std :: __ enable_shared_from_this 및 std :: __ weak_ptr과 함께 이것을 테스트했는데 잘 작동하는 것 같습니다 (gcc 4.9 및 gcc 5.2). 실제로 원자 연산이 생략되었는지 확인하기 위해 곧 프로파일 링 / 분해 할 것입니다.
Carl Cook

멋진 디테일! 에 설명 된대로 최근에 나는이 문제에 직면 이 질문에 결국 나 소스의 코드로 보이도록 만들어 것을, std::shared_ptr, std::__shared_ptr, __default_lock_policy등. 이 답변은 코드에서 이해 한 내용을 확인했습니다.
Nawaz

21

두 번째 질문은 왜 C ++ 11에서 제공되는 std :: shared_ptr의 비 원자 버전이 아니 었습니까? (이유가 있다고 가정).

침입 포인터가없는 이유나 공유 포인터의 다른 가능한 변형이없는 이유를 쉽게 물어볼 수 있습니다.

shared_ptrBoost에서 물려받은 의 디자인은 스마트 포인터의 최소 표준 lingua-franca를 만드는 것입니다. 일반적으로 말해서, 이것을 벽에서 떼어 내서 사용할 수 있습니다. 다양한 응용 분야에서 일반적으로 사용되는 것입니다. 인터페이스에 넣을 수 있으며 좋은 사람들이 기꺼이 사용할 가능성이 있습니다.

스레딩은 앞으로 많이 보급 될 것입니다. 실제로 시간이 지남에 따라 스레딩은 일반적으로 성능을 달성하는 주요 수단 중 하나입니다. 스레딩을 지원하는 데 필요한 최소한의 작업을 수행하기 위해 기본 스마트 포인터가 필요하면 이러한 현실이 가능해집니다.

사소한 차이가있는 6 개의 스마트 포인터를 표준에 덤핑하거나 정책 기반 스마트 포인터보다 더 나쁜 것은 끔찍했을 것입니다. 모든 사람은 자신이 가장 좋아하는 포인터를 선택하고 다른 모든 것을 맹세합니다. 아무도 다른 사람과 의사 소통 할 수 없습니다. 모든 사람이 자신의 유형을 가지고있는 C ++ 문자열의 현재 상황과 같습니다. 문자열과의 상호 운용이 스마트 포인터 클래스 간의 상호 운용보다 훨씬 쉽기 때문입니다.

Boost, 그리고 더 나아가위원회는 사용할 특정 스마트 포인터를 선택했습니다. 기능의 균형이 잘 맞았으며 실제로 널리 사용되고 일반적으로 사용되었습니다.

std::vector일부 코너 케이스에서도 네이 키드 어레이에 비해 비 효율성이 있습니다. 몇 가지 제한 사항이 있습니다. 일부 용도 vector는 던지는 할당자를 사용하지 않고. 그러나위원회는 vector모든 사람을위한 모든 것이되도록 설계하지 않았습니다 . 대부분의 응용 프로그램에서 좋은 기본값으로 설계되었습니다. 그것이 작동하지 않는 사람들은 그들의 필요에 맞는 대안을 작성할 수 있습니다.

shared_ptr원 자성이 부담 이라면 스마트 포인터를 위해 할 수있는 것처럼 . 그런 다음 다시 한 번, 너무 많이 복사하지 않는 것을 고려할 수도 있습니다.


7
+1은 "그렇게 많이 복사하지 않는 것도 고려할 수 있습니다."
알리

프로파일 러를 연결 한 적이 있다면, 당신은 특별하고 위와 같은 주장을 배제 할 수 있습니다. 충족하기 어려운 운영 요구 사항이없는 경우 C ++를 사용하지 마십시오. 당신처럼 주장하는 것은 고성능이나 낮은 지연 시간에 관심이있는 모든 사람들이 C ++를 보편적으로 욕하는 좋은 방법이며, 이것이 게임 프로그래머가 STL, 부스트 또는 예외를 사용하지 않는 이유입니다.
Hans Malherbe

명확성을 위해 귀하의 답변 상단에있는 인용문은 "왜 C ++ 11에서 제공되는 std :: shared_ptr의 비 원자 버전 이 아니 었 습니까?"라고 읽어야한다고 생각합니다.
Charles Savoie

4

직장에서 shared_ptr에 대한 강연을 준비 중입니다. 나는 (make_shared가 할 수있는 것과 같은) 별도의 malloc을 피하고 위에서 언급 한 shared_ptr_unsynchronized와 같은 잠금 정책에 대한 템플릿 매개 변수를 사용하여 수정 된 boost shared_ptr을 사용하고 있습니다. 나는 프로그램을 사용하고 있습니다

http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html

테스트로 불필요한 shared_ptr 복사본을 정리 한 후. 프로그램은 주 스레드 만 사용하며 테스트 인수가 표시됩니다. 테스트 환경은 linuxmint 14를 실행하는 노트북입니다. 다음은 초 단위의 시간입니다.

테스트 실행 설정 boost (1.49) std with make_shared 수정 된 boost
mt-unsafe (11) 11.9 9 / 11.5 (-pthread on) 8.4  
원자 (11) 13.6 12.4 13.0  
mt-unsafe (12) 113.5 85.8 / 108.9 (-pthread on) 81.5  
원자 (12) 126.0 109.1 123.6  

'std'버전 만 -std = cxx11을 사용하고 -pthread는 g ++ __shared_ptr 클래스에서 lock_policy를 전환 할 가능성이 높습니다.

이 수치를 통해 원자 적 지시가 코드 최적화에 미치는 영향을 알 수 있습니다. 테스트 케이스는 C ++ 컨테이너를 사용 vector<shared_ptr<some_small_POD>>하지 않지만 객체에 스레드 보호가 필요하지 않으면 문제가 발생할 수 있습니다. 추가 malloc이 인라인 및 코드 최적화의 양을 제한하기 때문에 Boost는 덜 영향을받습니다.

원자 명령의 확장 성을 스트레스 테스트하기에 충분한 코어가있는 머신을 아직 찾지 못했지만, 필요할 때만 std :: shared_ptr을 사용하는 것이 아마도 더 좋습니다.


3

Boost는 shared_ptr비 원자적인 것을 제공합니다 . 이라고 local_shared_ptr하며 boost의 스마트 포인터 라이브러리에서 찾을 수 있습니다.


인용이 좋은 짧고 견고한 답장에 +1하지만이 포인터 유형은 간접적 인 추가 수준 (local-> shared-> ptr vs shared-> ptr)으로 인해 메모리와 런타임 측면에서 비용이 많이 듭니다.
Red.Wave

@ Red.Wave 간접적 인 의미와 성능에 미치는 영향을 설명해 주시겠습니까? shared_ptr어차피 카운터가 있다는 뜻 인가요? 아니면 다른 문제가 있다는 뜻인가요? 문서는 유일한 차이점은 이것이 원자가 아니라는 것입니다.
양자 물리학 자

모든 로컬 ptr은 원래 공유 ptr에 대한 개수와 참조를 유지합니다. 따라서 최종 pointee에 대한 모든 액세스는 local에서 shared ptr 로의 derefrence가 필요하며, pointee에 대한 derefrence입니다. 따라서 공유 ptr의 간접 방향까지 쌓인 간접 방향이 하나 더 있습니다. 그리고 그것은 오버 헤드를 증가시킵니다.
Red.Wave

@ Red.Wave이 정보는 어디서 얻습니까? 이것은 "따라서 최종 pointee에 대한 접근은 local에서 shared ptr 로의 derefrence가 필요합니다"는 인용이 필요합니다. 부스트 문서에서 찾을 수 없습니다. 다시 말하지만, 내가 문서에서 본 것은 원자를 제외 local_shared_ptr하고 shared_ptr는 동일하다는 것입니다. local_shared_ptr고성능이 필요한 응용 프로그램에 사용하기 때문에 당신이 말하는 것이 사실인지 알아내는 데 진심으로 관심이 있습니다.
양자 물리학 자

3
@ Red.Wave 실제 소스 코드 github.com/boostorg/smart_ptr/blob/… 을 보면 이중 간접이 없다는 것을 알 수 있습니다. 문서의이 단락은 정신적 모델 일뿐입니다.
Ilya Popov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.