RTTI는 얼마나 비쌉니까?


152

RTTI를 사용하면 리소스가 적다는 것을 알고 있지만 얼마나 큽니까? 내가 본 곳 어디에서나 "RTTI는 비싸다"고 말하지만 실제로 메모리, 프로세서 시간 또는 속도를 보호하는 벤치 마크 또는 정량적 데이터를 제공하지는 않습니다.

RTTI는 얼마나 비쌉니까? RAM이 4MB 밖에없는 임베디드 시스템에서 사용할 수 있으므로 모든 비트가 중요합니다.

편집 : S. Lott의 답변 에 따라 실제로 내가하고있는 일을 포함시키는 것이 좋습니다. 클래스를 사용하여 길이가 다른 데이터를 전달하고 다른 작업을 수행 할 수 있으므로 가상 함수 만 사용 하여이 작업을 수행하기가 어렵습니다. 몇 가지를 사용 dynamic_cast하면 다른 파생 클래스가 다른 수준을 통과하면서도 완전히 다르게 작동하도록하여이 문제를 해결할 수 있는 것 같습니다 .

내 이해에서 dynamic_castRTTI를 사용하므로 제한된 시스템에서 사용하는 것이 얼마나 가능한지 궁금했습니다.


1
내가 편집 한 내용에 따라-몇 가지 동적 캐스트를 수행 할 때 매우 자주 방문자 패턴을 사용하면 문제가 다시 해결된다는 것을 알고 있습니다. 그게 당신을 위해 일할 수 있습니까?
philsquared 2009

4
나는 이것을 dynamic_castC ++에서 사용하기 시작했고 , 이제 디버거로 프로그램을 "중단"하면 내부 동적 캐스트 함수 내부에서 중단된다. 조금 느려
user541686

3
RTTI = "런타임 타입 정보"
Noumenon

답변:


115

컴파일러에 관계없이 여유가 있다면 언제든지 런타임에 저장할 수 있습니다.

if (typeid(a) == typeid(b)) {
  B* ba = static_cast<B*>(&a);
  etc;
}

대신에

B* ba = dynamic_cast<B*>(&a);
if (ba) {
  etc;
}

전자는 단지 하나의 비교만을 포함한다 std::type_info; 후자는 반드시 상속 트리와 비교를 순회하는 것을 포함합니다.

모든 사람들이 말했듯이 리소스 사용량은 구현에 따라 다릅니다.

제출자가 디자인상의 이유로 RTTI를 피해야한다는 다른 사람들의 의견에 동의합니다. 그러나 RTTI를 사용하는 데는 상당한 이유가 있습니다 (주로 boost :: any로 인해). 이를 염두에두고 일반적인 구현에서 실제 리소스 사용량을 아는 것이 유용합니다.

최근 GCC에서 RTTI에 대한 많은 연구를 수행했습니다.

tl; dr : GCC의 RTTI는 무시할만한 공간을 사용 typeid(a) == typeid(b)하며 많은 플랫폼 (Linux, BSD 및 아마도 임베디드 플랫폼이지만 mingw32는 아님)에서 매우 빠릅니다. 항상 축복받은 플랫폼에 있다는 것을 알고 있다면 RTTI는 거의 무료입니다.

구체적 세부 사항 :

GCC는 특정 "공급 업체 중립적"C ++ ABI [1]를 선호하며 항상이 ABI를 Linux 및 BSD 대상 [2]에 사용합니다. 이 ABI와 약한 연결을 지원하는 플랫폼의 typeid()경우 동적 연결 경계를 넘어서도 각 유형에 대해 일관되고 고유 한 객체를 반환합니다. &typeid(a) == &typeid(b)휴대용 테스트 typeid(a) == typeid(b)는 실제로 포인터를 내부적으로 비교 한다는 사실을 테스트 하거나 신뢰할 수 있습니다 .

GCC가 선호하는 ABI에서 클래스 vtable은 항상 유형별 RTTI 구조에 대한 포인터를 보유하지만 사용되지는 않습니다. 따라서 typeid()호출 자체 다른 vtable 조회 (가상 멤버 함수 호출과 동일)만큼 비용이 많이 들며 RTTI 지원 각 오브젝트에 추가 공간을 사용하지 않아야 합니다.

내가 알아낼 수있는 것에서, GCC (이 모든 서브 클래스 std::type_info) 에서 사용하는 RTTI 구조 는 이름 외에도 각 유형에 대해 몇 바이트 만 보유합니다. 로도 이름이 출력 코드에 있는지 여부는 분명하지 않습니다 -fno-rtti. 어느 쪽이든, 컴파일 된 바이너리의 크기 변화는 런타임 메모리 사용량의 변화를 반영해야합니다.

빠른 실험 (Ubuntu 10.04 64 비트에서 GCC 4.4.3 사용)은 -fno-rtti실제로 간단한 테스트 프로그램의 이진 크기가 몇 백 바이트 증가 함을 보여줍니다 . 이 조합을 통해 지속적으로 발생 -g하고 -O3. 왜 크기가 커질 지 잘 모르겠습니다. 한 가지 가능성은 GCC의 STL 코드가 RTTI없이 다르게 동작한다는 것입니다 (예외가 작동하지 않기 때문에).

[1] Itanium C ++ ABI라고하며 http://www.codesourcery.com/public/cxx-abi/abi.html에 문서화되어 있습니다. ABI 사양은 i686 / x86_64를 포함한 많은 아키텍처에서 작동하지만 이름은 끔찍한 혼란을 겪습니다. GCC 내부 소스 및 STL 코드의 주석은 이전에 사용한 "오래된"것과 대조적으로 Itanium을 "새로운"ABI라고합니다. 더 나쁜, "새로운"는 / 아이테니엄 ABI는를 의미 모든 통해 사용할 수있는 버전 -fabi-version; "오래된"ABI는이 버전 관리보다 우선합니다. GCC는 Itanium / versioned / "new"ABI 버전 3.0을 채택했습니다. "이전"ABI는 변경 기록을 올바르게 읽으면 2.95 이하에서 사용되었습니다.

[2] std::type_info플랫폼별로 리소스 목록 개체 안정성을 찾을 수 없습니다 . 내가 액세스 할 수있는 컴파일러의 경우 다음을 사용했습니다 echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES. 이 매크로 컨트롤의 동작 operator==을위한 std::type_infoGCC 3.0 등 GCC의 STL에서. mingw32-gcc는 Windows C ++ ABI를 준수한다는 것을 발견했습니다. 여기서 std::type_info객체는 DLL에서 유형에 따라 고유하지 않습니다. 커버 아래에 typeid(a) == typeid(b)전화 strcmp. 나는 링크 할 코드가없는 AVR과 같은 단일 프로그램 임베디드 대상에서 std::type_info객체가 항상 안정적 이라고 추측합니다 .


6
RTTI가 없으면 예외가 작동합니다. (당신은 던질 수 있고 int그에 대한 어떤
표도

3
@ 중복 제거기 : 그럼에도 불구하고 컴파일러에서 RTTI를 끄면 정상적으로 작동합니다. 실망 시켜서 죄송합니다.
Billy ONeal

5
예외 처리 메커니즘은 몇 가지 기본 요구 사항을 완전히 채우는 모든 유형에서 작동 할 수 있어야합니다. RTTI없이 모듈 경계에 걸쳐 임의 유형의 예외 처리 처리 방법을 자유롭게 제안 할 수 있습니다 . 업 캐스팅 및 다운 캐스팅이 필요하다는 것을 고려하십시오.
중복 제거기

15
typeid (a) == typeid (b)는 B * ba = dynamic_cast <B *> (& a)와 같지 않습니다. 파생 클래스 트리에서 임의 상속 수준으로 다중 상속되는 개체에서 시도하면 typeid () == typeid ()가 양수를 생성하지 않습니다. dynamic_cast는 상속 트리를 실제로 검색하는 유일한 방법입니다. RTTI를 비활성화하여 잠재적 인 절약에 대해 생각하지 말고 사용하십시오. 용량이 초과되면 코드 팽창을 최적화하십시오. 내부 루프 또는 다른 성능에 중요한 코드 내에서 dynamic_cast를 사용하지 마십시오.
mysticcoder

3
@mcoder 그렇기 때문에 기사에 명시 적으로 나와 the latter necessarily involves traversing an inheritance tree plus comparisons있습니다. @CoryB 전체 상속 트리에서 전송을 지원할 필요가 없을 때 "afford"할 수 있습니다. 예를 들어 컬렉션에서 X 유형의 모든 항목을 찾으려고하지만 X에서 파생 된 항목은 아닌 경우 이전 항목을 사용해야합니다. 파생 된 모든 인스턴스를 찾아야하는 경우에는 인스턴스를 사용해야합니다.
Aidiakapi

48

아마도이 수치가 도움이 될 것입니다.

나는 이것을 사용하여 빠른 테스트를하고 있었다 :

  • GCC Clock () + XCode의 프로파일 러.
  • 100,000,000 개의 루프 반복.
  • 2 x 2.66 GHz 듀얼 코어 Intel Xeon.
  • 해당 클래스는 단일 기본 클래스에서 파생됩니다.
  • typeid (). name ()은 "N12fastdelegate13FastDelegate1IivEE"를 반환합니다.

5 가지 사례가 테스트되었습니다.

1) dynamic_cast< FireType* >( mDelegate )
2) typeid( *iDelegate ) == typeid( *mDelegate )
3) typeid( *iDelegate ).name() == typeid( *mDelegate ).name()
4) &typeid( *iDelegate ) == &typeid( *mDelegate )
5) { 
       fastdelegate::FastDelegateBase *iDelegate;
       iDelegate = new fastdelegate::FastDelegate1< t1 >;
       typeid( *iDelegate ) == typeid( *mDelegate )
   }

5는 실제 코드입니다. 이미 가지고있는 것과 비슷한 지 확인하기 전에 해당 유형의 객체를 만들어야했습니다.

최적화없이

결과는 다음과 같습니다 (평균 몇 번 실행했습니다).

1)  1,840,000 Ticks (~2  Seconds) - dynamic_cast
2)    870,000 Ticks (~1  Second)  - typeid()
3)    890,000 Ticks (~1  Second)  - typeid().name()
4)    615,000 Ticks (~1  Second)  - &typeid()
5) 14,261,000 Ticks (~23 Seconds) - typeid() with extra variable allocations.

결론은 다음과 같습니다.

  • 최적화 typeid()가 없는 간단한 캐스트 사례의 경우 보다 2 배 이상 빠릅니다 dyncamic_cast.
  • 현대 기계에서이 둘의 차이는 약 1 나노초 (백만 분의 1 밀리 초)입니다.

최적화 (-O)

1)  1,356,000 Ticks - dynamic_cast
2)     76,000 Ticks - typeid()
3)     76,000 Ticks - typeid().name()
4)     75,000 Ticks - &typeid()
5)     75,000 Ticks - typeid() with extra variable allocations.

결론은 다음과 같습니다.

  • 최적화 된 간단한 캐스트 케이스의 경우 typeid()보다 약 20 배 빠릅니다 dyncamic_cast.

차트

여기에 이미지 설명을 입력하십시오

코드

주석에서 요청 한대로 코드는 아래에 있습니다 (약간 지저분하지만 작동합니다). 'FastDelegate.h'는 여기 에서 사용할 수 있습니다 .

#include <iostream>
#include "FastDelegate.h"
#include "cycle.h"
#include "time.h"

// Undefine for typeid checks
#define CAST

class ZoomManager
{
public:
    template < class Observer, class t1 >
    void Subscribe( void *aObj, void (Observer::*func )( t1 a1 ) )
    {
        mDelegate = new fastdelegate::FastDelegate1< t1 >;
        
        std::cout << "Subscribe\n";
        Fire( true );
    }
    
    template< class t1 >
    void Fire( t1 a1 )
    {
        fastdelegate::FastDelegateBase *iDelegate;
        iDelegate = new fastdelegate::FastDelegate1< t1 >;
        
        int t = 0;
        ticks start = getticks();
        
        clock_t iStart, iEnd;
        
        iStart = clock();
        
        typedef fastdelegate::FastDelegate1< t1 > FireType;
        
        for ( int i = 0; i < 100000000; i++ ) {
        
#ifdef CAST
                if ( dynamic_cast< FireType* >( mDelegate ) )
#else
                // Change this line for comparisons .name() and & comparisons
                if ( typeid( *iDelegate ) == typeid( *mDelegate ) )
#endif
                {
                    t++;
                } else {
                    t--;
                }
        }
        
        iEnd = clock();
        printf("Clock ticks: %i,\n", iEnd - iStart );
        
        std::cout << typeid( *mDelegate ).name()<<"\n";
        
        ticks end = getticks();
        double e = elapsed(start, end);
        std::cout << "Elasped: " << e;
    }
    
    template< class t1, class t2 >
    void Fire( t1 a1, t2 a2 )
    {
        std::cout << "Fire\n";
    }
    
    fastdelegate::FastDelegateBase *mDelegate;
};

class Scaler
{
public:
    Scaler( ZoomManager *aZoomManager ) :
        mZoomManager( aZoomManager ) { }
    
    void Sub()
    {
        mZoomManager->Subscribe( this, &Scaler::OnSizeChanged );
    }
    
    void OnSizeChanged( int X  )
    {
        std::cout << "Yey!\n";        
    }
private:
    ZoomManager *mZoomManager;
};

int main(int argc, const char * argv[])
{
    ZoomManager *iZoomManager = new ZoomManager();
    
    Scaler iScaler( iZoomManager );
    iScaler.Sub();
        
    delete iZoomManager;

    return 0;
}

1
물론 동적 캐스트가 더 일반적입니다. 항목이 더 파생 된 경우 작동합니다. 예 class a {}; class b : public a {}; class c : public b {};를 들어 대상이 인스턴스 인 경우 솔루션이 아닌 c클래스 b를 테스트 할 때 제대로 작동합니다 . 그럼에도 불구하고 합리적인하지만, 한dynamic_casttypeid
빌리 ONeal

34
이 벤치 마크는 최적화와 함께 완전히 가짜입니다 . typeid 검사는 루프 불변이며 루프 밖으로 이동합니다. 전혀 흥미롭지 않습니다. 기본 벤치마킹입니다.
Reinstate Monica

3
@ 쿠바 : 그렇다면 벤치 마크는 가짜입니다. 최적화를 해제 한 상태에서 벤치마킹해야하는 이유는 아닙니다. 이것이 더 나은 벤치 마크를 작성하는 이유입니다.
Billy ONeal

3
또 다시, 이것은 실패입니다. "최적화 된 캐스트 사례의 경우 typeid ()가 dyncamic_cast보다 거의 x20 빠릅니다." 그들은 같은 일을하지 않습니다. dynamic_cast가 더 느린 이유가 있습니다.
mysticcoder

1
@KubaOber : 총 +1 이것은 너무 고전적입니다. 그리고 이것이 일어난주기 수의 모습에서 분명해야합니다.
v.oddou

38

사물의 규모에 따라 다릅니다. 대부분의 경우 몇 가지 검사와 몇 가지 포인터 역 참조입니다. 대부분의 구현에서 가상 함수가있는 모든 오브젝트의 맨 위에는 해당 클래스에있는 가상 함수의 모든 구현에 대한 포인터 목록을 보유하는 vtable에 대한 포인터가 있습니다. 나는 대부분의 구현이 이것을 사용하여 클래스의 type_info 구조에 대한 다른 포인터를 저장한다고 생각합니다.

예를 들어 pseudo-c ++에서 :

struct Base
{
    virtual ~Base() {}
};

struct Derived
{
    virtual ~Derived() {}
};


int main()
{
    Base *d = new Derived();
    const char *name = typeid(*d).name(); // C++ way

    // faked up way (this won't actually work, but gives an idea of what might be happening in some implementations).
    const vtable *vt = reinterpret_cast<vtable *>(d);
    type_info *ti = vt->typeinfo;
    const char *name = ProcessRawName(ti->name);       
}

일반적으로 RTTI에 대한 실제 주장은 새로운 파생 클래스를 추가 할 때마다 어디에서나 코드를 수정해야한다는 유지 관리가 불가능하다는 것입니다. 모든 곳의 스위치 문 대신 가상 함수로 해당 문을 고려하십시오. 이렇게하면 클래스마다 다른 모든 코드가 클래스 자체로 이동하므로 새로운 파생은 모든 가상 함수를 재정의해야 완벽하게 작동하는 클래스가됩니다. 누군가가 클래스의 유형을 확인하고 다른 작업을 수행 할 때마다 큰 코드 기반을 찾아야한다면, 해당 스타일의 프로그래밍에서 벗어나는 법을 빨리 배우게 될 것입니다.

컴파일러에서 RTTI를 완전히 해제 할 수 있으면 RAM 공간이 작기 때문에 최종 결과 코드 크기를 크게 줄일 수 있습니다. 컴파일러는 가상 함수를 사용하여 모든 단일 클래스에 대해 type_info 구조를 생성해야합니다. RTTI를 끄면 이러한 모든 구조를 실행 가능 이미지에 포함시킬 필요는 없습니다.


4
RTTI를 사용하는 것이 나쁜 설계 결정으로 간주되는 이유를 실제로 설명해 주신 +1, 그것은 이전에는 분명하지 않았습니다.
aguazales

6
이 답변은 C ++의 힘에 대한 이해 수준이 낮습니다. "일반적으로"및 "대부분의 구현에서"는 자유로이 사용되므로 언어 ​​기능을 잘 사용하는 방법에 대해 생각하지 않고 있음을 의미합니다. 가상 기능과 RTTI를 다시 구현하는 것은 답이 아닙니다. RTTI가 답입니다. 때로는 객체가 특정 유형인지 알고 싶을 때가 있습니다. 그것이 거기에있는 이유입니다! 따라서 type_info 구조체에 몇 KB의 RAM이 손실됩니다. Gee ...
mysticcoder

16

글쎄, 프로파일 러는 절대 거짓말하지 않습니다.

나는 크게 변하지 않는 18-20 유형의 꽤 안정적인 계층 구조를 가지고 있기 때문에 간단한 열거 형 멤버를 사용 하여 트릭을 수행하고 RTTI의 "높은"비용을 피할 수 있을지 궁금했습니다 . RTTI가 실제로 if소개 하는 내용 보다 비싸면 회의적이었습니다 . 소년 아 소년 아

RTTI 비싸고 동등한 명령문 보다 훨씬 비싸 if거나 switchC ++의 기본 변수에 대해서는 단순 합니다. S. 로트의 대답은 완전히 정확하지 그래서이 있다 RTTI에 대한 추가 비용, 그건 하지 단지로 인해 성기를 갖는 if 믹스를. RTTI가 매우 비싸기 때문입니다.

이 테스트는 Apple LLVM 5.0 컴파일러에서 스톡 최적화가 켜진 상태에서 수행되었습니다 (기본 릴리스 모드 설정).

따라서 2 가지 기능이 있습니다. 각 기능은 1) RTTI 또는 2) 간단한 스위치를 통해 객체의 구체적인 유형을 나타냅니다. 그렇게 50,000,000 번입니다. 더 이상 고민하지 않고 50,000,000 회의 상대 런타임을 제시합니다.

여기에 이미지 설명을 입력하십시오

맞습니다 . 런타임의 94 %dynamicCasts소요되었습니다 . 그동안 블록 만했다 3.3 %를 .regularSwitch

긴 이야기를 짧게 : 당신이에 후크 할 수있는 에너지를 줄 수있는 경우에 enum나는 아래처럼 'D 타입은 RTTI를 할 필요가 있다면, 아마, 그것을 권하고 싶습니다 그리고 성능이 중요합니다. 멤버를 한 번만 설정 하면 ( 모든 생성자 를 통해 가져와야 함 ) 나중에 작성하지 마십시오.

즉, 이렇게하면 OOP 관행을 망칠 수 없습니다. 형식 정보를 사용할 수없고 RTTI를 사용하는 경우에만 사용해야합니다.

#include <stdio.h>
#include <vector>
using namespace std;

enum AnimalClassTypeTag
{
  TypeAnimal=1,
  TypeCat=1<<2,TypeBigCat=1<<3,TypeDog=1<<4
} ;

struct Animal
{
  int typeTag ;// really AnimalClassTypeTag, but it will complain at the |= if
               // at the |='s if not int
  Animal() {
    typeTag=TypeAnimal; // start just base Animal.
    // subclass ctors will |= in other types
  }
  virtual ~Animal(){}//make it polymorphic too
} ;

struct Cat : public Animal
{
  Cat(){
    typeTag|=TypeCat; //bitwise OR in the type
  }
} ;

struct BigCat : public Cat
{
  BigCat(){
    typeTag|=TypeBigCat;
  }
} ;

struct Dog : public Animal
{
  Dog(){
    typeTag|=TypeDog;
  }
} ;

typedef unsigned long long ULONGLONG;

void dynamicCasts(vector<Animal*> &zoo, ULONGLONG tests)
{
  ULONGLONG animals=0,cats=0,bigcats=0,dogs=0;
  for( ULONGLONG i = 0 ; i < tests ; i++ )
  {
    for( Animal* an : zoo )
    {
      if( dynamic_cast<Dog*>( an ) )
        dogs++;
      else if( dynamic_cast<BigCat*>( an ) )
        bigcats++;
      else if( dynamic_cast<Cat*>( an ) )
        cats++;
      else //if( dynamic_cast<Animal*>( an ) )
        animals++;
    }
  }

  printf( "%lld animals, %lld cats, %lld bigcats, %lld dogs\n", animals,cats,bigcats,dogs ) ;

}

//*NOTE: I changed from switch to if/else if chain
void regularSwitch(vector<Animal*> &zoo, ULONGLONG tests)
{
  ULONGLONG animals=0,cats=0,bigcats=0,dogs=0;
  for( ULONGLONG i = 0 ; i < tests ; i++ )
  {
    for( Animal* an : zoo )
    {
      if( an->typeTag & TypeDog )
        dogs++;
      else if( an->typeTag & TypeBigCat )
        bigcats++;
      else if( an->typeTag & TypeCat )
        cats++;
      else
        animals++;
    }
  }
  printf( "%lld animals, %lld cats, %lld bigcats, %lld dogs\n", animals,cats,bigcats,dogs ) ;  

}

int main(int argc, const char * argv[])
{
  vector<Animal*> zoo ;

  zoo.push_back( new Animal ) ;
  zoo.push_back( new Cat ) ;
  zoo.push_back( new BigCat ) ;
  zoo.push_back( new Dog ) ;

  ULONGLONG tests=50000000;

  dynamicCasts( zoo, tests ) ;
  regularSwitch( zoo, tests ) ;
}

13

표준 방식 :

cout << (typeid(Base) == typeid(Derived)) << endl;

표준 RTTI는 기본 문자열 비교에 의존하기 때문에 비싸므로 RTTI의 속도는 클래스 이름 길이에 따라 달라질 수 있습니다.

문자열 비교가 사용되는 이유는 라이브러리 / DLL 경계에서 일관되게 작동하도록하기 위해서입니다. 응용 프로그램을 정적으로 빌드하거나 특정 컴파일러를 사용하는 경우 다음을 사용할 수 있습니다.

cout << (typeid(Base).name() == typeid(Derived).name()) << endl;

작동이 보장되지는 않지만 (오탐의 가능성은 없지만 오탐의 가능성이 있음) 최대 15 배 더 빠를 수 있습니다. 이것은 typeid () 구현에 의존하여 특정 방식으로 작동하며 내부 문자 포인터를 비교하기 만하면됩니다. 이것은 때때로 다음과 같습니다 :

cout << (&typeid(Base) == &typeid(Derived)) << endl;

당신은 할 수 있지만 매우 빠른 유형이 일치하는 경우가 될 것이다 안전하게 하이브리드를 사용하고 타의 추종을 불허하는 유형에 대한 최악의 경우가 될 것입니다 :

cout << ( typeid(Base).name() == typeid(Derived).name() || 
          typeid(Base) == typeid(Derived) ) << endl;

이를 최적화해야하는지 여부를 이해하려면 패킷을 처리하는 데 걸리는 시간과 비교하여 새 패킷을 얻는 데 소요되는 시간을 확인해야합니다. 대부분의 경우 문자열 비교는 아마도 큰 오버 헤드가 아닐 것입니다. (클래스 또는 네임 스페이스에 따라 :: 클래스 이름 길이)

이것을 최적화하는 가장 안전한 방법은 Base 클래스의 일부로 자신의 typeid를 int (또는 enum Type : int)로 구현하고 클래스 유형을 결정하는 데 사용하고 static_cast <> 또는 reinterpret_cast < >

나에게 차이는 최적화되지 않은 MS VS 2005 C ++ SP1에서 약 15 배입니다.


2
"표준 RTTI는 기본 문자열 비교를 수행하기 때문에 비용이 많이 듭니다"-아니오, 이에 대한 "표준"은 없습니다. 그것은 구현의 typeid::operator작동 방식 입니다. 예를 들어 지원되는 플랫폼의 GCC는 gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/…를char * 강요하지 않고 이미 s 비교를 사용 하고 있습니다. 물론, 당신의 방법은 MSVC가 플랫폼에서 기본값보다 훨씬 더 잘 작동하게합니다. 그래서 나는 포인터를 기본적으로 사용하는 "일부 대상"이 무엇인지 알지 못합니다 ... 그러나 내 요점은 MSVC의 행동이 어떤 방식도 아닙니다 "표준".
underscore_d

7

간단한 확인을 위해 RTTI는 포인터 비교만큼 저렴할 수 있습니다. 상속 검사의 strcmp경우 dynamic_cast하나의 구현에서 맨 위에서 맨 아래로 상속하는 경우 상속 트리의 모든 유형에 비해 비용이 많이들 수 있습니다 .

dynamic_cast& typeid (...) == & typeid (type)를 통해 명시 적으로 형식을 사용하지 않고 검사 하여 오버 헤드를 줄일 수도 있습니다 . .dll 또는 기타 동적으로로드 된 코드에서는 반드시 작동하지는 않지만 정적으로 링크 된 항목에는 매우 빠릅니다.

그 시점에서 switch 문을 사용하는 것과 같지만 거기에 있습니다.


1
strcmp 버전에 대한 참조가 있습니까? 유형 검사에 strcmp를 사용하는 것은 매우 비효율적이고 부정확 한 것 같습니다.
JaredPar

유형별로 여러 개의 type_info 객체를 가질 수있는 잘못된 구현에서는 bool type_info :: operator == (const type_info & x) const를 "! strcmp (name (), x.name ())"으로 구현할 수 있습니다.
Greg Rogers

3
MSVC의 경우 dynamic_cast 또는 typeid (). operator == 디스 어셈블리를 시작하면 strcmp가 발생합니다. 다른 .dll로 컴파일 된 형식과 비교하는 끔찍한 경우가 있다고 가정합니다. 그리고 맹 글링 된 이름을 사용하므로 적어도 동일한 컴파일러가 주어지면 정확합니다.
MSN

1
"typeid (...) == typeid (type)"을 수행하고 주소를 비교하지 않아야합니다.
Johannes Schaub-litb

1
내 요점은 당신이 & typeid (...) == & typeid (blah)을 초기에 할 수 있으며 안전하다는 것입니다. typeid (...)가 스택에서 생성 될 수 있기 때문에 실제로는 유용한 기능이 없지만 주소가 같으면 유형이 같습니다.
MSN

6

항상 측정하는 것이 가장 좋습니다. 다음 코드에서 g ++ 하에서는 수작업으로 코드화 된 유형 식별을 사용하는 것이 RTTI보다 약 3 배 더 빠른 것으로 보입니다. 문자 대신 문자열을 사용하는보다 현실적인 수동 코딩 구현이 느려질 것이므로 타이밍이 가깝습니다.

#include <iostream>
using namespace std;

struct Base {
    virtual ~Base() {}
    virtual char Type() const = 0;
};

struct A : public Base {
    char Type() const {
        return 'A';
    }
};

struct B : public Base {;
    char Type() const {
        return 'B';
    }
};

int main() {
    Base * bp = new A;
    int n = 0;
    for ( int i = 0; i < 10000000; i++ ) {
#ifdef RTTI
        if ( A * a = dynamic_cast <A*> ( bp ) ) {
            n++;
        }
#else
        if ( bp->Type() == 'A' ) {
            A * a = static_cast <A*>(bp);
            n++;
        }
#endif
    }
    cout << n << endl;
}

1
dynamic_cast를 사용하지 말고 typeid를 사용하십시오. 성능을 향상시킬 수 있습니다.
Johannes Schaub-litb

1
하지만 dynamic_cast를 사용하는 것이 더 현실적입니다. 적어도 내 코드를 보면

2
bp가 A에서 파생 된 형식을 가리키는 지 여부도 확인합니다. == 'A'는 정확히 'A'를 가리키는 지 확인합니다. 또한 테스트가 다소 불공평하다고 생각합니다. 컴파일러는 bp가 A와 다른 것을 가리킬 수 없다는 것을 쉽게 볼 수 있지만 여기서는 최적화되지 않는다고 생각합니다.
Johannes Schaub-litb

어쨌든, 나는 당신의 코드를 테스트했습니다. RTTI의 경우 "0.016s", 가상 함수 호출의 경우 "0.044s"가 표시됩니다. (-O2 사용)
Johannes Schaub-litb

typeid를 사용하도록 변경해도 여기에는 아무런 차이가 없습니다 (여전 0.016 초)
Johannes Schaub-litb

4

얼마 전에 저는 3ghz PowerPC에 대한 MSVC 및 GCC의 특정 사례에서 RTTI의 시간 비용을 측정했습니다. 테스트에서 내가 쳤 dynamic_cast<>는지 또는 놓쳤는 지에 따라 0.8μs ~ 2μs 사이의 비용이 들었 습니다 (심층 트리가있는 상당히 큰 C ++ 앱) .


2

RTTI는 얼마나 비쌉니까?

그것은 전적으로 사용중인 컴파일러에 달려 있습니다. 일부는 문자열 비교를 사용하고 다른 일부는 실제 알고리즘을 사용한다는 것을 알고 있습니다.

유일한 희망은 샘플 프로그램을 작성하고 컴파일러가 수행하는 작업을 확인하는 것입니다 (또는 적어도 백만 dynamic_casts또는 백만 typeid초 를 실행하는 데 걸리는 시간을 결정하는 것 ).


1

RTTI는 저렴할 수 있으며 strcmp가 필요하지 않습니다. 컴파일러는 실제 계층 구조를 역순으로 수행하도록 테스트를 제한합니다. 따라서 클래스 A의 자식 인 클래스 B의 자식 인 클래스 C가있는 경우 A * ptr에서 C * ptr 로의 dynamic_cast는 하나의 포인터 비교만을 의미하며 두 개가 아닌 (BTW, vptr 테이블 포인터 만) 비교). 테스트는 "if (vptr_of_obj == vptr_of_C) return (C *) obj"와 같습니다.

또 다른 예는 A *에서 B *로 dynamic_cast를 시도하는 경우입니다. 이 경우 컴파일러는 두 경우 (obj는 C, obj는 B)를 차례로 확인합니다. 가상 함수 테이블은 집계로 만들어지기 때문에 단일 테스트 (대부분의 경우)로 단순화 할 수도 있으므로 테스트는 "if (offset_of (vptr_of_obj, B) == vptr_of_B)"

offset_of = return sizeof (vptr_table)> = sizeof (vptr_of_B)? vptr_of_new_methods_in_B : 0

의 메모리 레이아웃

vptr_of_C = [ vptr_of_A | vptr_of_new_methods_in_B | vptr_of_new_methods_in_C ]

컴파일러는 컴파일 타임에 이것을 최적화하는 방법을 어떻게 알고 있습니까?

컴파일시 컴파일러는 객체의 현재 계층 구조를 알고 있으므로 다른 유형의 계층 구조 dynamic_casting을 컴파일하지 않습니다. 그런 다음 계층 깊이를 처리하고 그러한 깊이와 일치하도록 반전 테스트 양을 추가해야합니다.

예를 들어 컴파일되지 않습니다.

void * something = [...]; 
// Compile time error: Can't convert from something to MyClass, no hierarchy relation
MyClass * c = dynamic_cast<MyClass*>(something);  

-5

RTTI 비교를 수행 할 때마다 if 문을 추가했기 때문에 RTTI는 "고가"가 될 수 있습니다. 깊이 중첩 된 반복에서는 비용이 많이들 수 있습니다. 루프에서 결코 실행되지 않는 것은 본질적으로 무료입니다.

선택은 적절한 다형성 디자인을 사용하여 if 문을 제거하는 것입니다. 깊게 중첩 된 루프에서는 성능에 필수적입니다. 그렇지 않으면별로 중요하지 않습니다.

또한 RTTI는 서브 클래스 계층 구조를 가릴 수 있기 때문에 비용이 많이 듭니다. "객체 지향 프로그래밍"에서 "객체 지향"을 제거하는 부작용이있을 수 있습니다.


2
반드시 그런 것은 아닙니다-dynamic_cast를 통해 간접적으로 사용하고 계층 구조를 그대로 유지하려고했습니다. 각 하위 유형에 다른 (가변 크기) 데이터를 가져야하기 때문에 다운 캐스트해야하므로 dynamic_cast입니다.
Cristián Romo

1
@ Cristián Romo : 새로운 사실로 질문을 업데이트하십시오. dynamic_cast는 C ++에서 (때로는) 필요한 악입니다. RTTI 성능을 요구할 때 RTTI 성능에 대해 묻는 것은 그리 의미가 없습니다.
S.Lott

@ S.Lott : 업데이트되었습니다. 혼란에 대해 죄송합니다.
Cristián Romo

1
내가 그랬어 실험 지금이 약을 - RTTI가보다 훨씬 더 비싼 밖으로는 회전 if런타임 타입 정보이 방법을 확인할 때 소개 문.
bobobobo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.