programing tip

C ++의 일반 포인터와 비교하여 스마트 포인터의 오버 헤드는 얼마입니까?

itbloger 2020. 9. 18. 07:53
반응형

C ++의 일반 포인터와 비교하여 스마트 포인터의 오버 헤드는 얼마입니까?


C ++ 11의 일반 포인터에 비해 스마트 포인터의 오버 헤드는 얼마입니까? 즉, 스마트 포인터를 사용하면 내 코드가 느려지고, 그렇다면 얼마나 느려질까요?

특히 C ++ 11 std::shared_ptrstd::unique_ptr.

분명히 스택 아래로 밀려 난 물건은 더 커질 것입니다 (적어도 그렇게 생각합니다). 스마트 포인터도 내부 상태 (참조 횟수 등)를 저장해야하기 때문에 질문은 실제로 얼마나 될 것인가입니다. 내 성능에 영향을 미치나요?

예를 들어 일반 포인터 대신 함수에서 스마트 포인터를 반환합니다.

std::shared_ptr<const Value> getValue();
// versus
const Value *getValue();

또는 예를 들어 내 함수 중 하나가 일반 포인터 대신 스마트 포인터를 매개 변수로 허용하는 경우 :

void setValue(std::shared_ptr<const Value> val);
// versus
void setValue(const Value *val);

std::unique_ptr 사소하지 않은 삭제자를 제공하는 경우에만 메모리 오버 헤드가 있습니다.

std::shared_ptr 매우 작지만 항상 참조 카운터에 대한 메모리 오버 헤드가 있습니다.

std::unique_ptr 생성자 동안 (제공된 삭제자를 복사하고 / 또는 포인터를 null로 초기화해야하는 경우) 및 소멸자 동안 (소유 된 개체를 제거하기 위해)에만 시간 오버 헤드가 있습니다.

std::shared_ptr생성자 (참조 카운터 생성), 소멸자 (참조 카운터 감소 및 가능하면 객체 파괴) 및 할당 연산자 (참조 카운터 증가)에 시간 오버 헤드가 있습니다. 의 스레드 안전성 보장으로 인해 std::shared_ptr이러한 증가 / 감소는 원자 적이므로 약간의 오버 헤드가 추가됩니다.

이들 중 어느 것도 역 참조 (소유 된 객체에 대한 참조를 얻는 데)에 시간 오버 헤드가없는 반면,이 작업은 포인터에 대해 가장 일반적인 것으로 보입니다.

요약하면 약간의 오버 헤드가 있지만 스마트 포인터를 지속적으로 생성하고 파괴하지 않는 한 코드가 느려지지 않아야합니다.


모든 코드 성능과 마찬가지로 하드 정보를 얻을 수있는 유일한 방법은 기계 코드 측정 및 / 또는 검사하는 것 입니다.

즉, 간단한 추론은

  • 디버그 빌드에서 약간의 오버 헤드를 예상 할 수 있습니다. 예를 들어 operator->함수 호출로 실행해야 단계에 들어갈 수 있기 때문입니다 (이는 클래스 및 함수를 비디 버그로 표시하는 데 대한 일반적인 지원 부족으로 인한 것입니다).

  • 들어 shared_ptr그 제어 블록의 동적 할당을 포함, 동적 할당이 매우 느린 ++ C의 다른 기본 동작보다 이후 당신은 (사용을 초기 생성에 약간의 오버 헤드를 기대할 수 있습니다 make_shared때 실질적으로 가능한 한 그 오버 헤드를 최소화하기 위해).

  • 또한 shared_ptr예를 들어 shared_ptrby 값을 전달할 때 참조 횟수를 유지하는 데 약간의 오버 헤드가 있지만에 대한 오버 헤드는 없습니다 unique_ptr.

위의 첫 번째 사항을 염두에두고 측정 할 때 디버그 및 릴리스 빌드 모두에 대해 수행하십시오.

국제 C ++ 표준화위원회는 발표했다 성능에 대한 기술 보고서를 하기 전에, 그러나 이것은 2006 년이었다 unique_ptrshared_ptr표준 라이브러리에 추가되었습니다. 그럼에도 불구하고 스마트 포인터는 그 시점에서 오래된 모자 였으므로 보고서는 그것을 고려했습니다. 관련 부분 인용 :

“사소한 스마트 포인터를 통해 값에 액세스하는 것이 일반 포인터를 통해 값에 액세스하는 것보다 훨씬 느리다면 컴파일러는 추상화를 비효율적으로 처리합니다. 과거에는 대부분의 컴파일러에 상당한 추상화 패널티가 있었지만 현재의 여러 컴파일러는 여전히 그렇습니다. 그러나 적어도 2 개의 컴파일러가 1 % 미만의 추상화 패널티와 3 %의 패널티가있는 것으로보고되었으므로 이러한 종류의 오버 헤드를 제거하는 것은 최신 기술 내에 있습니다. "

정보에 입각 한 추측에 따르면, 2014 년 초 현재 가장 인기있는 컴파일러를 통해 "최신 기술 수준"을 달성했습니다.


내 대답은 다른 사람들과 다르며 그들이 코드를 프로파일 링했는지 정말로 궁금합니다.

shared_ptr은 제어 블록에 대한 메모리 할당 (참조 카운터와 모든 약한 참조에 대한 포인터 목록을 유지함) 때문에 생성에 상당한 오버 헤드가 있습니다. 또한 std :: shared_ptr이 항상 2 개의 포인터 튜플 (하나는 객체, 하나는 제어 블록)이기 때문에 엄청난 메모리 오버 헤드가 있습니다.

shared_pointer를 값 매개 변수로 함수에 전달하면 일반 호출보다 10 배 이상 느려지고 스택 해제를 위해 코드 세그먼트에 많은 코드가 생성됩니다. 참조로 전달하면 성능 측면에서 훨씬 더 나빠질 수있는 추가 간접 정보를 얻을 수 있습니다.

따라서 기능이 소유권 관리에 실제로 관여하지 않는 한 이렇게하면 안됩니다. 그렇지 않으면 "shared_ptr.get ()"을 사용하십시오. 정상적인 함수 호출 중에 객체가 죽지 않도록 설계되지 않았습니다.

If you go mad and use shared_ptr on small objects like an abstract syntax tree in a compiler or on small nodes in any other graph structure you will see a huge perfomance drop and a huge memory increase. I have seen a parser system which was rewritten soon after C++14 hit the market and before the programmer learned to use smart pointers correctly. The rewrite was a magnitude slower then the old code.

It is not a silver bullet and raw pointers aren't bad by definition either. Bad programmers are bad and bad design is bad. Design with care, design with clear ownership in mind and try to use the shared_ptr mostly on the subsystem API boundary.

If you want to learn more you can watch Nicolai M. Josuttis good talk about "The Real Price of Shared Pointers in C++" https://vimeo.com/131189627
It goes deep into the implementation details and CPU architecture for write barriers, atomic locks etc. once listening you will never talk about this feature being cheap. If you just want a proof of the magnitude slower, skip the first 48 minutes and watch him running example code which runs upto 180 times slower (compiled with -O3) when using shared pointer everywhere.


In other words, is my code going to be slower if I use smart pointers, and if so, how much slower?

Slower? Most likely not, unless you are creating a huge index using shared_ptrs and you have not enough memory to the point that your computer starts wrinkling, like an old lady being plummeted to the ground by an unbearable force from afar.

What would make your code slower is sluggish searches, unnecessary loop processing, huge copies of data, and a lot of write operations to disk (like hundreds).

The advantages of a smart pointer are all related to management. But is the overhead necessary? This depends on your implementation. Let's say you are iterating over an array of 3 phases, each phase has an array of 1024 elements. Creating a smart_ptr for this process might be overkill, since once the iteration is done you'll know you have to erase it. So you could gain extra memory from not using a smart_ptr...

하지만 정말 그렇게 하시겠습니까?

단일 메모리 누수로 인해 제품이 제 시간에 오류 지점을 가질 수 있습니다 (프로그램이 매시간 4 메가 ​​바이트를 누수한다고 가정 해 봅시다. 컴퓨터를 중단하는 데 몇 달이 걸리 겠지만, 누수가 있기 때문에 알 수 있습니다) .

"당신의 소프트웨어는 3 개월 동안 보증됩니다. 그러면 저에게 서비스를 요청하십시오."라고 말하는 것과 같습니다.

그래서 결국은 정말 문제입니다 ...이 위험을 감당할 수 있습니까? 수백 개의 다른 개체에 대한 인덱싱을 처리하기 위해 원시 포인터를 사용하는 것은 메모리 제어를 잃을 가치가 있습니다.

대답이 예이면 원시 포인터를 사용하십시오.

고려하고 싶지 않다면 a smart_ptr는 훌륭하고 실행 가능하며 멋진 솔루션입니다.


Just for a glimpse and just for the [] operator,it is ~5X slower than the raw pointer as demonstrated in the following code, which was compiled using gcc -lstdc++ -std=c++14 -O0 and outputted this result:

malloc []:     414252610                                                 
unique []  is: 2062494135                                                
uq get []  is: 238801500                                                 
uq.get()[] is: 1505169542
new is:        241049490 

I'm beginning to learn c++, I got this in my mind: you always need to know what are you doing and take more time to know what others had done in your c++.

EDIT

As methioned by @Mohan Kumar, I provided more details. The gcc version is 7.4.0 (Ubuntu 7.4.0-1ubuntu1~14.04~ppa1), The above result was obtained when the -O0 is used, however, when I use '-O2' flag, I got this:

malloc []:     223
unique []  is: 105586217
uq get []  is: 71129461
uq.get()[] is: 69246502
new is:        9683

Then shifted to clang version 3.9.0, -O0 was :

malloc []:     409765889
unique []  is: 1351714189
uq get []  is: 256090843
uq.get()[] is: 1026846852
new is:        255421307

-O2 was:

malloc []:     150
unique []  is: 124
uq get []  is: 83
uq.get()[] is: 83
new is:        54

The result of clang -O2 is amazing.

#include <memory>
#include <iostream>
#include <chrono>
#include <thread>

uint32_t n = 100000000;
void t_m(void){
    auto a  = (char*) malloc(n*sizeof(char));
    for(uint32_t i=0; i<n; i++) a[i] = 'A';
}
void t_u(void){
    auto a = std::unique_ptr<char[]>(new char[n]);
    for(uint32_t i=0; i<n; i++) a[i] = 'A';
}

void t_u2(void){
    auto a = std::unique_ptr<char[]>(new char[n]);
    auto tmp = a.get();
    for(uint32_t i=0; i<n; i++) tmp[i] = 'A';
}
void t_u3(void){
    auto a = std::unique_ptr<char[]>(new char[n]);
    for(uint32_t i=0; i<n; i++) a.get()[i] = 'A';
}
void t_new(void){
    auto a = new char[n];
    for(uint32_t i=0; i<n; i++) a[i] = 'A';
}

int main(){
    auto start = std::chrono::high_resolution_clock::now();
    t_m();
    auto end1 = std::chrono::high_resolution_clock::now();
    t_u();
    auto end2 = std::chrono::high_resolution_clock::now();
    t_u2();
    auto end3 = std::chrono::high_resolution_clock::now();
    t_u3();
    auto end4 = std::chrono::high_resolution_clock::now();
    t_new();
    auto end5 = std::chrono::high_resolution_clock::now();
    std::cout << "malloc []:     " <<  (end1 - start).count() << std::endl;
    std::cout << "unique []  is: " << (end2 - end1).count() << std::endl;
    std::cout << "uq get []  is: " << (end3 - end2).count() << std::endl;
    std::cout << "uq.get()[] is: " << (end4 - end3).count() << std::endl;
    std::cout << "new is:        " << (end5 - end4).count() << std::endl;
}

참고URL : https://stackoverflow.com/questions/22295665/how-much-is-the-overhead-of-smart-pointers-compared-to-normal-pointers-in-c

반응형