C ++ RTTI를 사용하는 것이 바람직하지 않게 만드는 이유는 무엇입니까?
LLVM 문서를 보면, 그들은 언급 들은 "RTTI의 사용자 지정 양식"을 사용 , 이것은 그들이 가지고있는 이유 isa<>
, cast<>
그리고 dyn_cast<>
템플릿 기능.
일반적으로 라이브러리가 언어의 일부 기본 기능을 다시 구현한다는 것을 읽는 것은 끔찍한 코드 냄새이며 실행을 초대합니다. 그러나 이것은 우리가 이야기하고있는 LLVM입니다. 사람들은 C ++ 컴파일러 와 C ++ 런타임에서 작업하고 있습니다. 그들이 무엇을하는지 모른다면, 나는 Mac OS와 함께 제공 clang
되는 gcc
버전을 선호 하기 때문에 꽤 엉망 입니다.
그래도 그들보다 경험이 적기 때문에 일반 RTTI의 함정이 무엇인지 궁금합니다. v-table이있는 유형에서만 작동한다는 것을 알고 있지만 두 가지 질문 만 제기합니다.
- vtable을 가지려면 가상 메서드가 필요하기 때문에 메서드를 다음과 같이 표시하지 않는 이유는
virtual
무엇입니까? 가상 소멸자는 이것을 잘하는 것 같습니다. - 솔루션이 일반 RTTI를 사용하지 않는 경우 어떻게 구현되었는지 알 수 있습니까?
LLVM이 자체 RTTI 시스템을 사용하는 데에는 몇 가지 이유가 있습니다. 이 시스템은 간단하고 강력 하며 LLVM 프로그래머 매뉴얼 섹션에 설명되어 있습니다. 다른 포스터가 지적했듯이 코딩 표준 은 C ++ RTTI에 대해 1) 공간 비용과 2) 사용 성능 저하라는 두 가지 주요 문제를 제기합니다.
RTTI의 공간 비용은 상당히 높습니다. vtable (적어도 하나의 가상 메서드)이있는 모든 클래스는 클래스 이름과 기본 클래스에 대한 정보를 포함하는 RTTI 정보를 얻습니다. 이 정보는 typeid 연산자와 dynamic_cast 를 구현하는 데 사용됩니다 . 이 비용은 vtable이있는 모든 클래스에 대해 지불되기 때문에 (vtable이 RTTI 정보를 가리 키기 때문에 PGO 및 링크 시간 최적화는 도움이되지 않습니다) LLVM은 -fno-rtti로 빌드됩니다. 경험적으로 이것은 실행 파일 크기의 5-10 % 정도를 절약 할 수 있습니다. LLVM은 typeid에 해당하는 것이 필요하지 않으므로 각 클래스의 이름 (type_info의 다른 것들 중에서)을 유지하는 것은 공간 낭비 일뿐입니다.
성능 저하는 벤치마킹을 수행하거나 간단한 작업을 위해 생성 된 코드를 보면 매우 쉽게 확인할 수 있습니다. LLVM isa <> 연산자는 일반적으로 단일로드 및 상수와의 비교로 컴파일됩니다 (클래스가 classof 메서드를 구현하는 방법에 따라이를 제어하지만). 다음은 간단한 예입니다.
#include "llvm/Constants.h"
using namespace llvm;
bool isConstantInt(Value *V) { return isa<ConstantInt>(V); }
이것은 다음과 같이 컴파일됩니다.
$ clang t.cc -S -o--O3 -I $ HOME / llvm / include -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -mkernel -fomit-frame-pointer ... __Z13isConstantIntPN4llvm5ValueE : cmpb $ 9, 8 (% rdi) % al 설정 movzbl % al, % eax ret
(어셈블리를 읽지 않으면) 부하이며 상수와 비교합니다. 대조적으로 dynamic_cast와 동등한 것은 다음과 같습니다.
#include "llvm/Constants.h"
using namespace llvm;
bool isConstantInt(Value *V) { return dynamic_cast<ConstantInt*>(V) != 0; }
다음과 같이 컴파일됩니다.
clang t.cc -S -o--O3 -I $ HOME / llvm / include -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -mkernel -fomit-frame-pointer ... __Z13isConstantIntPN4llvm5ValueE : pushq % rax xorb % al, % al testq % rdi, % rdi je LBB0_2 xorl % esi, % esi movq $ -1, % rcx xorl % edx, % edx callq ___dynamic_cast testq % rax, % rax setne % al LBB0_2 : movzbl % al, % eax popq % rdx ret
이것은 훨씬 더 많은 코드이지만, 킬러는 __dynamic_cast에 대한 호출입니다.이 호출은 RTTI 데이터 구조를 샅샅이 뒤지고 매우 일반적이고 동적으로 계산 된 작업을 수행해야합니다. 이것은 부하 및 비교보다 몇 배 더 느립니다.
Ok, ok, so it's slower, why does this matter? This matters because LLVM does a LOT of type checks. Many parts of the optimizers are built around pattern matching specific constructs in the code and performing substitutions on them. For example, here is some code for matching a simple pattern (which already knows that Op0/Op1 are the left and right hand side of an integer subtract operation):
// (X*2) - X -> X
if (match(Op0, m_Mul(m_Specific(Op1), m_ConstantInt<2>())))
return Op1;
The match operator and m_* are template metaprograms that boil down to a series of isa/dyn_cast calls, each of which has to do a type check. Using dynamic_cast for this sort of fine-grained pattern matching would be brutally and showstoppingly slow.
Finally, there is another point, which is one of expressivity. The different 'rtti' operators that LLVM uses are used to express different things: type check, dynamic_cast, forced (asserting) cast, null handling etc. C++'s dynamic_cast doesn't (natively) offer any of this functionality.
In the end, there are two ways to look at this situation. On the negative side, C++ RTTI is both overly narrowly defined for what many people want (full reflection) and is too slow to be useful for even simple things like what LLVM does. On the positive side, the C++ language is powerful enough that we can define abstractions like this as library code, and opt out of using the language feature. One of my favorite things about C++ is how powerful and elegant libraries can be. RTTI isn't even very high among my least favorite features of C++ :) !
-Chris
The LLVM coding standards seem to answer this question fairly well:
In an effort to reduce code and executable size, LLVM does not use RTTI (e.g. dynamic_cast<>) or exceptions. These two language features violate the general C++ principle of "you only pay for what you use", causing executable bloat even if exceptions are never used in the code base, or if RTTI is never used for a class. Because of this, we turn them off globally in the code.
That said, LLVM does make extensive use of a hand-rolled form of RTTI that use templates like isa<>, cast<>, and dyn_cast<>. This form of RTTI is opt-in and can be added to any class. It is also substantially more efficient than dynamic_cast<>.
Here is a great article on RTTI and why you might need to roll your own version of it.
I'm not an expert on the C++ RTTI, but I have implemented my own RTTI as well because there are definitely reasons why you would need to do that. First, the C++ RTTI system is not very feature-rich, basically all you can do is type casting and getting basic information. What if, at run-time, you have a string with the name of a class, and you want to construct an object of that class, good luck doing this with C++ RTTI. Also, C++ RTTI is not really (or easily) portable across modules (you cannot identify the class of an object that was created from another module (dll/so or exe). Similarly, the C++ RTTI's implementation is specific to the compiler, and it typically is expensive to turn on in terms of additional overhead to implement this for all types. Finally, it is not really persistent, so it can't really be used for file saving/loading for example (e.g. you might want to save the data of an object to a file, but you would also want to save the "typeid" of its class, such that, at load time, you know which object to create in order to load this data, that cannot be done reliably with C++ RTTI). For all or some of these reasons, many frameworks have their own RTTI (from very simple to very feature-rich). Examples are wxWidget, LLVM, Boost.Serialization, etc.. this really isn't that uncommon.
Since you just need a virtual method to have a vtable, why don't they just mark a method as virtual? Virtual destructors seem to be good at this.
That's probably what their RTTI system uses too. Virtual functions are the basis for dynamic binding (run-time binding), and thus, it is basically required for doing any kind of run-time type identification/information (not just required by the C++ RTTI, but any implementation of RTTI will have to rely on virtual calls in one way or another).
If their solution doesn't use regular RTTI, any idea how it was implemented?
Sure, you can look up RTTI implementations in C++. I have done my own and there are many libraries that have their own RTTI as well. It is fairly simple to write, really. Basically, all you need is a means to uniquely represent a type (i.e. the name of the class, or some mangled version of it, or even a unique ID for each class), some sort of structure analogous to type_info
that contains all the information about the type that you need, then you need a "hidden" virtual function in each class that will return this type information on request (if this function is overriden in each derived class, it will work). There are, of course, some additional things that can be done, like a singleton repository of all types, maybe with associated factory functions (this can be useful for creating objects of a type when all that is known at run-time is the name of the type, as a string or the type ID). Also, you may wish to add some virtual functions to allow dynamic type casting (usually this is done by calling the most-derived class' cast function and performing static_cast
up to the type that you wish to cast to).
The predominant reason is that they struggle to keep memory usage as low as possible.
RTTI is only available for classes which feature at least one virtual method, which means that instances of the class will contain a pointer to the virtual table.
On a 64-bits architecture (which is common today), a single pointer is 8 bytes. Since the compiler instantiate lots of small objects, this adds up pretty quickly.
Therefore there is an ongoing effort to remove virtual functions as much as possible (and practical), and implement what would have been virtual functions with the switch
instruction, which has similar execution speed but a significantly lower memory impact.
Their constant worry for memory consumption has paid off, in that Clang consumes significantly less memory than gcc, for example, which is important when you offer the library to clients.
On the other hand, it also means that adding a new kind of node usually results in editing code in a good number of files because each switch need to be adapted (thankfully compilers issue warning if you miss an enum member in a switch). So they accepted to make maintenance a tad more difficult in the name of memory efficiency.
참고URL : https://stackoverflow.com/questions/5134975/what-can-make-c-rtti-undesirable-to-use
'Program Tip' 카테고리의 다른 글
Apache HttpClient 작성 다중 파트 양식 게시 (0) | 2020.11.11 |
---|---|
커밋 된 스냅 샷 및 스냅 샷 격리 수준 읽기 (0) | 2020.11.11 |
솔루션 전체에서 균일 한 버전 관리를위한 공유 AssemblyInfo (0) | 2020.11.11 |
이미지가있는 버튼, ImageButton 및 클릭 가능한 ImageView의 차이점은 무엇입니까? (0) | 2020.11.11 |
MySQL Workbench 다크 테마 (0) | 2020.11.11 |