RTTI แพงแค่ไหน?


152

ฉันเข้าใจว่ามีทรัพยากรที่ได้รับผลกระทบจากการใช้ RTTI แต่มันมีขนาดใหญ่เพียงใด ทุกที่ที่ฉันดูก็บอกว่า "RTTI มีราคาแพง" แต่ไม่มีพวกเขาจริง ๆ ที่ให้มาตรฐานหรือข้อมูลเชิงปริมาณเกี่ยวกับหน่วยความจำเวลาประมวลผลหรือความเร็ว

ดังนั้น RTTI แพงแค่ไหน? ฉันอาจใช้กับระบบสมองกลฝังตัวที่ฉันมี RAM เพียง 4MB ดังนั้นทุกบิตจึงนับ

แก้ไข: ตามคำตอบของ S. Lottมันจะดีกว่าถ้าฉันรวมสิ่งที่ฉันกำลังทำอยู่ ฉันใช้คลาสเพื่อส่งผ่านข้อมูลที่มีความยาวต่างกันและสามารถดำเนินการต่างๆ ได้ดังนั้นจึงเป็นการยากที่จะทำได้โดยใช้ฟังก์ชันเสมือนเท่านั้น ดูเหมือนว่าใช้ไม่กี่dynamic_castสามารถแก้ไขปัญหานี้ได้โดยอนุญาตให้คลาสที่ได้รับแตกต่างกันถูกส่งผ่านระดับที่แตกต่างกัน

จากความเข้าใจของฉันdynamic_castใช้ RTTI ดังนั้นฉันสงสัยว่ามันจะเป็นไปได้อย่างไรที่จะใช้ในระบบที่ จำกัด


1
การติดตามจากการแก้ไขของคุณบ่อยครั้งเมื่อฉันพบว่าตัวเองกำลังทำ casts หลาย ๆ แบบฉันรู้ว่าการใช้รูปแบบผู้เข้าชมทำให้สิ่งต่าง ๆ กลับมาเหมือนเดิมอีกครั้ง สามารถใช้ได้สำหรับคุณ
philsquared

4
ฉันจะใช้วิธีนี้ - ฉันเพิ่งเริ่มใช้dynamic_castใน C ++ และตอนนี้ 9 จาก 10 ครั้งเมื่อฉัน "หยุด" โปรแกรมด้วยโปรแกรมดีบั๊กมันจะแบ่งภายในฟังก์ชั่นไดนามิกคาสท์ภายใน มันช้ามาก
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 (ส่วนใหญ่เนื่องจากการเพิ่ม :: ใด ๆ ) ในใจมันมีประโยชน์ที่จะรู้ว่าการใช้ทรัพยากรจริงในการใช้งานทั่วไป

เมื่อเร็ว ๆ นี้ฉันได้ทำการวิจัยเกี่ยวกับ RTTI ใน GCC

tl; dr: RTTI ใน GCC ใช้พื้นที่ที่ไม่สำคัญและtypeid(a) == typeid(b)รวดเร็วมากในหลาย ๆ แพลตฟอร์ม (Linux, BSD และแพลตฟอร์มแบบฝังตัว แต่ไม่ใช่ mingw32) หากคุณรู้ว่าคุณจะอยู่บนแพลตฟอร์มที่มีความสุข RTTI อยู่ใกล้กับฟรีมาก

รายละเอียดทราย:

GCC ชอบที่จะใช้ C ++ ABI "เป็นกลางผู้ขาย" โดยเฉพาะและใช้ ABI นี้สำหรับเป้าหมาย Linux และ BSD เสมอ [2] สำหรับแพลตฟอร์มที่สนับสนุน ABI นี้และการเชื่อมโยงที่อ่อนแอให้typeid()ส่งคืนวัตถุที่สอดคล้องและไม่ซ้ำกันสำหรับแต่ละประเภทแม้ข้ามขอบเขตการลิงก์แบบไดนามิก คุณสามารถทดสอบ&typeid(a) == &typeid(b)หรือเพียงแค่ใช้ความจริงที่ว่าการทดสอบแบบพกพาtypeid(a) == typeid(b)นั้นเปรียบเทียบตัวชี้ภายในเท่านั้น

ใน ABI ที่ต้องการของ GCC คลาส vtable จะถือตัวชี้ไปยังโครงสร้าง RTTI ต่อประเภทแม้ว่าจะไม่สามารถใช้งานได้ ดังนั้นการtypeid()เรียกใช้ควรมีราคาเท่ากันกับการค้นหา vtable อื่น ๆ (เช่นเดียวกับการเรียกฟังก์ชันสมาชิกเสมือน) และการสนับสนุน RTTI ไม่ควรใช้พื้นที่พิเศษสำหรับแต่ละวัตถุ

จากสิ่งที่ฉันสามารถทำได้โครงสร้าง RTTI ที่ใช้โดย GCC (นี่คือคลาสย่อยทั้งหมดของstd::type_info) เก็บสองสามไบต์เท่านั้นสำหรับแต่ละประเภทนอกเหนือจากชื่อ ฉันยังไม่ชัดเจนว่าชื่อมีอยู่ในรหัสผลลัพธ์หรือ-fno-rttiไม่ ทั้งสองวิธีการเปลี่ยนแปลงขนาดของไบนารีที่คอมไพล์ควรสะท้อนถึงการเปลี่ยนแปลงในการใช้งานหน่วยความจำรันไทม์

การทดสอบอย่างรวดเร็ว (โดยใช้ GCC 4.4.3 บน Ubuntu 10.04 64 บิต) แสดงว่า-fno-rttiจริง ๆ แล้วเพิ่มขนาดไบนารีของโปรแกรมทดสอบอย่างง่ายเพียงไม่กี่ร้อยไบต์ นี้เกิดขึ้นอย่างต่อเนื่องในการรวมกันของและ-g -O3ฉันไม่แน่ใจว่าทำไมขนาดจะเพิ่มขึ้น สิ่งหนึ่งที่เป็นไปได้คือรหัส STL ของ GCC นั้นทำงานแตกต่างกันหากไม่มี RTTI (เนื่องจากข้อยกเว้นจะไม่ทำงาน)

[1] รู้จักในชื่อ Itanium C ++ ABI, บันทึกที่ http://www.codesourcery.com/public/cxx-abi/abi.html ชื่อนั้นสร้างความสับสนอย่างมาก: ชื่อนั้นอ้างถึงสถาปัตยกรรมการพัฒนาดั้งเดิมแม้ว่าข้อมูลจำเพาะของ ABI นั้นจะทำงานบนสถาปัตยกรรมจำนวนมากรวมถึง i686 / x86_64 ความคิดเห็นในแหล่งที่มาภายในของ GCC และรหัส STL อ้างถึง Itanium ว่าเป็น "ใหม่" ABI ตรงกันข้ามกับ "เก่า" ที่พวกเขาเคยใช้มาก่อน ยิ่งไปกว่านั้น "ใหม่" / Itanium ABI หมายถึงทุกรุ่นที่มีให้ผ่าน-fabi-version; ABI "เก่า" ได้กำหนดเวอร์ชันไว้ล่วงหน้าแล้ว GCC รับรอง ABI Itanium / versioned / "new" ในเวอร์ชั่น 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_infoใน STL ของ GCC ณ GCC 3.0 ฉันพบว่า mingw32-gcc เป็นไปตาม Windows C ++ ABI ซึ่งstd::type_infoวัตถุไม่ได้มีลักษณะเฉพาะสำหรับประเภทใน DLLs; typeid(a) == typeid(b)โทรstrcmpภายใต้ฝาครอบ ฉันคาดการณ์ว่าในเป้าหมายฝังตัวโปรแกรมเดียวเช่น AVR ซึ่งไม่มีรหัสที่จะเชื่อมโยงstd::type_infoวัตถุต่างๆจะมีเสถียรภาพเสมอ


6
ข้อยกเว้นทำงานโดยไม่มี RTTI (คุณได้รับอนุญาตให้โยนintและไม่มี vtable ในเรื่องนั้น :))
Billy ONeal

3
@Dupuplicator: และยังเมื่อฉันปิด RTTI ในคอมไพเลอร์ของฉันพวกเขาทำงานได้ดี ขอโทษที่ทำให้คุณผิดหวัง
Billy ONeal

5
กลไกการจัดการข้อยกเว้นจะต้องสามารถทำงานกับประเภทใด ๆ เติมเต็มความต้องการขั้นพื้นฐานบางอย่าง คุณมีอิสระที่จะแนะนำวิธีจัดการกับการขว้างปาและจับข้อยกเว้นของชนิดที่กำหนดเองข้ามขอบเขตของโมดูลโดยไม่มี RTTI โปรดพิจารณาว่าจำเป็นต้องมีการขึ้น - ลง
Deduplicator

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 คุณสามารถ "จ่าย" ที่จะทำเมื่อคุณไม่จำเป็นต้องสนับสนุนการคัดเลือกจากต้นไม้ที่สืบทอดทั้งหมด ตัวอย่างเช่นหากคุณต้องการค้นหารายการทั้งหมดของประเภท X ในคอลเล็กชัน แต่ไม่ใช่รายการที่ได้มาจาก X ดังนั้นสิ่งที่คุณควรใช้คือรายการก่อนหน้า หากคุณต้องการค้นหาอินสแตนซ์ที่ได้รับทั้งหมดคุณจะต้องใช้อินสแตนซ์หลัง
Aidiakapi

48

บางทีตัวเลขเหล่านี้อาจช่วยได้

ฉันทำแบบทดสอบด่วนโดยใช้สิ่งนี้:

  • GCC Clock () + Profiler ของ XCode
  • วนซ้ำ 100,000,000
  • Intel Xeon Dual-Core 2 x 2.66 GHz
  • คลาสที่มีปัญหามาจากคลาสพื้นฐานเดียว
  • 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()dyncamic_cast
  • บนเครื่องจักรที่ทันสมัยความแตกต่างระหว่างทั้งสองนั้นประมาณ 1 นาโนวินาที (หนึ่งล้านมิลลิวินาที)

ด้วยการเพิ่มประสิทธิภาพ (-Os)

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()เกือบ x20 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_castแต่ไม่ได้อยู่กับtypeidวิธีการแก้ปัญหา แม้ว่าจะยังคงสมเหตุสมผล +1
Billy ONeal

34
มาตรฐานนี้เป็นการปลอมทั้งหมดที่มีการปรับให้เหมาะสม : การตรวจสอบ typeid นั้นไม่แปรเปลี่ยนแบบวนซ้ำและถูกย้ายออกจากลูป มันไม่น่าสนใจเลยมันเป็นพื้นฐานการเปรียบเทียบแบบไม่มี -
Reinstate Monica

3
@Kuba: จากนั้นมาตรฐานเป็นของปลอม นั่นไม่ใช่เหตุผลในการเปรียบเทียบกับการปรับให้เหมาะสม นั่นเป็นเหตุผลที่จะเขียนมาตรฐานที่ดีกว่า
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
+1 สำหรับการอธิบายว่าทำไมการใช้ RTTI จึงเป็นการตัดสินใจออกแบบที่ไม่ดีซึ่งไม่ชัดเจนสำหรับฉันมาก่อน
aguazales

6
คำตอบนี้คือความเข้าใจในระดับต่ำของพลังของ C ++ "โดยทั่วไป" และ "ในการติดตั้งใช้งานส่วนใหญ่" ที่ใช้อย่างอิสระหมายความว่าคุณไม่ได้คิดถึงวิธีการใช้คุณสมบัติภาษาต่างๆอย่างดี ฟังก์ชั่นเสมือนจริงและการใช้ RTTI ซ้ำไม่ใช่คำตอบ RTTI คือคำตอบ บางครั้งคุณแค่อยากรู้ว่าวัตถุนั้นเป็นวัตถุประเภทใด นั่นเป็นเหตุผลที่มันมี! ดังนั้นคุณจะสูญเสีย RAM บางส่วนไปที่โครงสร้างของ type_info Gee ...
mysticcoder

16

ผู้สร้างโปรไฟล์ไม่เคยโกหก

เนื่องจากฉันมีลำดับชั้นที่ค่อนข้างคงที่ของ 18-20 ประเภทที่ไม่เปลี่ยนแปลงมากนักฉันสงสัยว่าถ้าเพียงใช้สมาชิก enum ที่เรียบง่ายจะทำการหลอกลวงและหลีกเลี่ยงต้นทุน RTTI โดยอ้างว่า "สูง" ฉันสงสัยถ้า RTTI มีราคาแพงกว่าifข้อความที่แนะนำ บอยโอ้เด็กชายมันคือ

แต่กลับกลายเป็นว่า RTTI เป็นราคาแพงมากขึ้นมีราคาแพงกว่าเทียบเท่าifคำสั่งหรือง่ายswitchในตัวแปรดั้งเดิมใน C ++ ดังนั้นคำตอบของ S.Lott ไม่ถูกต้องสมบูรณ์มีเป็นค่าใช้จ่ายเพิ่มเติมสำหรับ RTTI และก็ไม่ได้เนื่องจากเป็นเพียงแค่การมีifคำสั่งในการผสม เป็นเพราะ RTTI นั้นมีราคาแพงมาก

การทดสอบนี้ทำกับคอมไพเลอร์ Apple LLVM 5.0 โดยเปิดการปรับแต่งสต็อกไว้ (การตั้งค่าโหมดรีลีสเริ่มต้น)

ดังนั้นฉันจึงมีฟังก์ชั่นด้านล่าง 2 ฟังก์ชันซึ่งแต่ละประเภทจะคำนวณประเภทวัตถุที่เป็นรูปธรรมไม่ว่าจะผ่าน 1) RTTI หรือ 2) สวิตช์ที่เรียบง่าย มันทำได้ 50,000,000 ครั้ง โดยไม่มีความกังวลใจเพิ่มเติมฉันนำเสนอ runtimes สัมพัทธ์สำหรับ 50,000,000 รัน

ป้อนคำอธิบายรูปภาพที่นี่

ถูกต้องแล้วdynamicCastsใช้เวลารันไทม์94% ในขณะที่regularSwitchบล็อกเอาแค่3.3%

เรื่องสั้นสั้น ๆ : ถ้าคุณสามารถใช้พลังงานในการ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;

เพื่อให้เข้าใจว่าคุณต้องการปรับให้เหมาะสมหรือไม่คุณต้องดูว่าคุณใช้เวลาในการรับแพ็คเก็ตใหม่นานเท่าใดเทียบกับเวลาที่ใช้ในการประมวลผลแพ็กเก็ต ในกรณีส่วนใหญ่การเปรียบเทียบสตริงอาจไม่เป็นค่าใช้จ่ายที่มีขนาดใหญ่ (ขึ้นอยู่กับคลาสหรือเนมสเปซ :: ความยาวชื่อคลาส)

วิธีที่ปลอดภัยที่สุดในการปรับให้เหมาะสมคือการใช้ typeid ของคุณเองเป็น int (หรือ enum Type: int) เป็นส่วนหนึ่งของคลาสฐานของคุณและใช้เพื่อกำหนดประเภทของคลาสจากนั้นใช้ static_cast <> หรือ reinterpret_cast < >

สำหรับฉันความแตกต่างคือประมาณ 15 ครั้งใน MS VS 2005 C ++ SP1 ที่ไม่ได้เพิ่มประสิทธิภาพ


2
"RTTI มาตรฐานมีราคาแพงเพราะอาศัยการเปรียบเทียบสตริงพื้นฐาน" - ไม่ไม่มี "Standard" เกี่ยวกับเรื่องนี้ มันเป็นเพียงวิธีการดำเนินงานของtypeid::operatorการทำงานของ GCC บนแพลตฟอร์มที่สนับสนุนเช่นแล้วใช้การเปรียบเทียบของchar *s โดยไม่ต้องให้เราบังคับให้มัน - gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/... แน่นอนว่าวิธีการของคุณทำให้ MSVC ทำงานได้ดีกว่าการเริ่มต้นบนแพลตฟอร์มของคุณดังนั้นความรุ่งโรจน์และฉันไม่รู้ว่า "เป้าหมายบางอย่าง" ที่ใช้ตัวชี้คืออะไร ... แต่ประเด็นของฉันคือพฤติกรรมของ MSVC ไม่ได้อยู่ในทางใดทางหนึ่ง "มาตรฐาน".
underscore_d

7

สำหรับการตรวจสอบอย่างง่าย RTTI อาจมีราคาถูกเท่ากับการเปรียบเทียบตัวชี้ สำหรับการตรวจสอบการสืบทอดอาจมีค่าใช้จ่ายสูงเท่ากับstrcmpสำหรับทุกชนิดในแผนผังการสืบทอดหากคุณdynamic_castเริ่มต้นจากบนลงล่าง

คุณสามารถลดค่าใช้จ่ายโดยไม่ใช้ dynamic_castและตรวจสอบประเภทอย่างชัดเจนผ่าน & typeid (... ) == & typeid (type) ในขณะที่ไม่จำเป็นต้องทำงานกับ. dll หรือรหัสที่โหลดแบบไดนามิกอื่น ๆ มันอาจค่อนข้างเร็วสำหรับสิ่งต่าง ๆ ที่มีการเชื่อมโยงแบบคงที่

แม้ว่า ณ จุดนั้นมันเหมือนการใช้คำสั่งเปลี่ยนดังนั้นคุณไป


1
คุณมีผู้อ้างอิงสำหรับรุ่น strcmp หรือไม่? ดูเหมือนว่าไม่มีประสิทธิภาพมากและไม่ถูกต้องในการใช้ strcmp สำหรับการตรวจสอบประเภท
JaredPar

ในการใช้งานที่ไม่ดีที่อาจมีหลายวัตถุ type_info ต่อประเภทมันสามารถใช้ Bool type_info :: ผู้ประกอบการ == (const type_info & x) const เป็น "! strcmp (ชื่อ (), x.name ())"
Greg Rogers

3
ขั้นตอนในการถอดแยกชิ้นส่วนของ dynamic_cast หรือ typeid (). โอเปอเรเตอร์ == สำหรับ MSVC และคุณจะพบ strcmp ในนั้น ฉันคิดว่ามันมีกรณีที่น่ากลัวที่คุณเปรียบเทียบกับประเภทที่รวบรวมใน. dll อื่น และมันใช้ชื่อ mangled ดังนั้นอย่างน้อยก็ถูกต้องเพราะคอมไพเลอร์ตัวเดียวกัน
MSN

1
คุณควรจะทำ "typeid (... ) == typeid (type)" และไม่เปรียบเทียบที่อยู่
Johannes Schaub - litb

1
ประเด็นของฉันคือคุณสามารถทำได้ & typeid (... ) == & typeid (blah) เร็วและปลอดภัย จริง ๆ แล้วมันอาจจะไม่ทำสิ่งใดที่มีประโยชน์เนื่องจาก typeid (... ) สามารถสร้างได้บนสแต็ก แต่ถ้าที่อยู่ของพวกเขาเท่ากันประเภทของพวกเขาจะเท่ากัน
MSN

6

เป็นการดีที่สุดที่จะวัดสิ่งต่าง ๆ ในรหัสต่อไปนี้ภายใต้ g ++ การใช้รหัสประจำตัวแบบมือดูเหมือนจะเร็วกว่า RTTI ประมาณสามเท่า ฉันแน่ใจว่ามีการใช้งานจริงมากขึ้นการเขียนโค้ดที่ใช้สตริงแทนตัวอักษรจะช้าลงทำให้การกำหนดเวลาใกล้กัน ..

#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

ต่อไปฉันได้ทดสอบโค้ดของคุณแล้ว และมันทำให้ฉัน "0.016s" สำหรับ RTTI และ "0.044s" สำหรับการเรียกใช้ฟังก์ชันเสมือน (ใช้ -O2)
Johannes Schaub - litb

แม้ว่าการเปลี่ยนเป็น typeid จะไม่สร้างความแตกต่างใด ๆ ที่นี่ (ยัง 0.016s)
Johannes Schaub - litb

4

เมื่อไม่นานมานี้ฉันวัดค่าเวลาสำหรับ RTTI ในกรณีเฉพาะของ MSVC และ GCC สำหรับ PowerG 3ghz ในการทดสอบฉันใช้งาน (แอป C ++ ขนาดใหญ่พอสมควรที่มีdynamic_cast<>แผนผังระดับลึก) แต่ละค่าใช้จ่ายระหว่าง0.8μsและ2μsขึ้นอยู่กับว่ามันจะตีหรือพลาด


2

ดังนั้น RTTI แพงแค่ไหน?

ขึ้นอยู่กับคอมไพเลอร์ที่คุณใช้ ฉันเข้าใจว่ามีบางคนใช้การเปรียบเทียบสตริงและอื่น ๆ ใช้อัลกอริทึมจริง

ความหวังเดียวของคุณคือการเขียนโปรแกรมตัวอย่างและดูสิ่งที่คอมไพเลอร์ของคุณไม่ (หรืออย่างน้อยตรวจสอบเท่าใดเวลาที่จะใช้เวลาในการดำเนินการเป็นล้านdynamic_castsหรือล้านtypeidวินาที)


1

RTTI อาจมีราคาถูกและไม่จำเป็นต้องมี strcmp คอมไพเลอร์ จำกัด การทดสอบเพื่อดำเนินการลำดับชั้นที่แท้จริงในลำดับย้อนกลับ ดังนั้นถ้าคุณมีคลาส C ที่เป็นลูกของคลาส B ซึ่งเป็นลูกของคลาส A, dynamic_cast จาก A * ptr ไปยัง C * ptr บ่งบอกถึงการเปรียบเทียบตัวชี้เดียวเท่านั้นและไม่ใช่สอง (BTW เฉพาะตัวชี้ตาราง vptr คือ เมื่อเทียบ) การทดสอบเป็นเช่น "if (vptr_of_obj == vptr_of_C) return (C *) obj"

อีกตัวอย่างหนึ่งถ้าเราพยายาม dynamic_cast จาก A * ถึง B * ในกรณีนั้นคอมไพเลอร์จะตรวจสอบทั้งสองกรณี (obj เป็น C และ obj เป็น B) สิ่งนี้สามารถทำให้ง่ายขึ้นสำหรับการทดสอบเดี่ยว (เกือบทุกครั้ง) เนื่องจากตารางฟังก์ชันเสมือนเป็นการรวมกันดังนั้นการทดสอบจะกลับเป็น "ถ้า (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-time ทุกครั้งที่คุณทำการเปรียบเทียบ RTTI ในการทำซ้ำที่ซ้อนกันสิ่งนี้อาจมีราคาแพง ในบางสิ่งที่ไม่เคยถูกดำเนินการในลูปมันฟรีเป็นหลัก

ทางเลือกคือใช้การออกแบบ polymorphic ที่เหมาะสมกำจัดคำสั่ง if ในลูปที่ซ้อนกันอย่างลึกล้ำนี่เป็นสิ่งจำเป็นสำหรับการแสดง ไม่อย่างนั้นมันไม่สำคัญมาก

RTTI นั้นมีราคาแพงเช่นกันเพราะมันสามารถปิดบังลำดับชั้นของคลาสย่อย (ถ้ามี) มันอาจมีผลข้างเคียงของการลบ "object oriented" จาก "การเขียนโปรแกรมเชิงวัตถุ"


2
ไม่จำเป็น - ฉันจะใช้มันทางอ้อมผ่าน dynamic_cast และทำให้ลำดับชั้นอยู่ในตำแหน่งเพราะฉันจำเป็นต้องลดความเร็วลงเนื่องจากแต่ละประเภทย่อยต้องมีข้อมูลที่แตกต่างกัน
Cristián Romo

1
@ Cristián Romo: โปรดอัปเดตคำถามของคุณด้วยข้อเท็จจริงใหม่เหล่านี้ dynamic_cast เป็นสิ่งชั่วร้ายที่จำเป็น (ในบางครั้ง) ใน C ++ การถามเกี่ยวกับการแสดง RTTI เมื่อคุณถูกบังคับให้ทำมันไม่สมเหตุสมผลเลย
S.Lott

@ S.Lott: อัปเดต ขออภัยเกี่ยวกับความสับสน
Cristián Romo

1
ฉันทำการทดลองเกี่ยวกับสิ่งนี้ในตอนนี้ - ปรากฎว่า RTTI มีราคาแพงกว่าifคำแถลงที่คุณแนะนำเมื่อคุณตรวจสอบข้อมูลประเภทรันไทม์ด้วยวิธีนี้
bobobobo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.