TL;DR
LLVM(Low Level Vertual Machine)はコンパイラの基盤であり、RustやSwiftのコンパイラとしても利用されています。
あわせて、LLVM自体では一部C++が利用されており、そこの公式ドキュメントではコード規約が含まれています。
特にC++の例外処理およびRTTIの利用については、コードとバイナリサイズの低減から利用をしないようにすると明言されています。
Do not use RTTI or Exceptions
In an effort to reduce code and executable size, LLVM does not use exceptions or RTTI (runtime type information, for example, dynamic_cast<>).
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.
https://llvm.org/docs/CodingStandards.html#do-not-use-rtti-or-exceptions
実際にどれくらいの差が出るのでしょうか?
本記事では実際にコードをコンパイルし、バイナリサイズを計測して検証します。
想定読者
- C++初学者
- パフォーマンスの最適化に興味がある方
話すこと / 話さないこと
- 話すこと
- RTTIとはなにか
- LLVMにおけるRTTIの扱いとサイズの比較
- 話さないこと
- LLVMの詳細なアーキテクチャや内部実装
- コンパイラの最適化アルゴリズムの詳細
用語の説明
まず、いくつか専門用語を説明します。
RTTIとは
Run-Time Type Informationの略で、実行時型情報と訳されます。
実行時に安全な型の変換をしたり、型情報を取得するための機能です。
Run-time type information - Wikipedia
C++においては次の内容が該当します。
typeidのドキュメントでは、型の確認の方法について説明されています。
また、dynamic_castの公式ドキュメントでは、継承が行われた際にいつcastの操作ができるかの説明をされていて、失敗したらnullptrになることがわかります。
(他重継承について明確化するための、仮想継承おけるsidecastについても紹介されています)
加えて、こちらの引数はpolymorphic typeである必要があります(targetはpolymophic typeである必要はないです)。
polymorphic typeとは、Object#Polymorphic_objectsに記載がありますが、
少なくとも1つのvirtual functionを持つオブジェクトのことです。
また、RTTIを使わない場合、dyn_castやisa, castといったテンプレートベースの関数を利用する必要があります。
下記の公式ドキュメントに実際にどのようにdyn_castを利用するかの例が記載されています。
How to set up LLVM-style RTTI for your class hierarchy — LLVM 22.0.0git documentation
Exceptionとは
いわゆる例外処理です。
Exception handling (programming) - Wikipedia
C++においては次の内容が該当します。
Go言語などでは存在しないです。くわしくは、以下の記事などを参考にしてください。
「例外」がないからGo言語はイケてないとかって言ってるヤツが本当にイケてない件 #ネタ - Qiita
実測
コードの詳細や実行方法の詳細は下記のリポジトリを参考にしてください。
比較基準
今回の比較基準は以下の通りです。
- コードサイズ:
ls -lhでバイナリサイズを確認 - バイナリ:
size <binary>でセクションごとのサイズを確認
環境構築
LLVMはC++17で開発されているため、そちらの環境に合わせて環境構築をして実測を行います。
Unless otherwise documented, LLVM subprojects are written using standard C++17 code and avoid unnecessary vendor-specific extensions.
https://llvm.org/docs/CodingStandards.html#c-standard-versions
devboxで環境を整えます。
https://gcc.gnu.org/projects/cxx-status.html#cxx17 を読むと、GCC15でC++17がサポートされているとあるため、gccの15.2を追加します。
合わせて、LLVMのコードを利用する必要があるため、llvmPackages.libllvmを追加します。
RTTIの有効化・無効化のコード準備
次の2つのコードを用意しました。
RTTI有効な場合のコード(test_rtti.cpp):
#include <iostream>
struct Base {
virtual ~Base() {}
};
struct Derived : Base {
int value = 42;
};
int main() {
Base* ptr = new Derived();
if (Derived* d = dynamic_cast<Derived*>(ptr)) {
std::cout << "RTTI: value = " << d-> value << std::endl;
delete ptr;
return d->value;
}
delete ptr;
return 0;
};
RTTI無効な場合のコード(test_llvm_cast.cpp):
#include "llvm/Support/Casting.h"
#include <iostream>
enum Kind { BaseKind, DerivedKind };
struct Base {
Kind BK;
Base(Kind K): BK(K) {}
virtual ~Base() {}
Kind getKind() const { return BK; }
};
struct Derived : Base {
int value = 42;
Derived() : Base(DerivedKind) {}
static bool classof(const Base* B) {
return B->getKind() == DerivedKind;
}
};
int main() {
Base *ptr = new Derived();
if(Derived* d = llvm::dyn_cast<Derived>(ptr)) {
std::cout << "VLLM value: " << d-> value << std::endl;
delete ptr;
return d->value;
}
delete ptr;
return 0;
};
How to set up LLVM-style RTTI for your class hierarchy — LLVM 22.0.0git documentationに記載の通り、
classofやenumを利用して、RTTIを模倣する必要があります。
コンパイル
今回は最適化レベルO2でコンパイルを行いました。
また、header情報を付与するために、llvm-config --cxxflagsを利用しています。
devbox shell
g++ -std=c++17 -O2 test_rtti.cpp -o test_rtti
g++ -std=c++17 -O2 \
$(llvm-config --cxxflags) \
test_llvm_cast.cpp -o test_llvm_cast
計測結果
❯ ls -lh test_*
-rwxrwxr-x 1 ** ** 17K 12月 11 11:25 test_llvm_cast
-rw-r--r-- 1 ** ** 589 12月 11 10:27 test_llvm_cast.cpp
-rwxrwxr-x 1 ** ** 17K 12月 11 10:29 test_rtti
-rw-r--r-- 1 ** ** 328 12月 11 10:26 test_rtti.cpp
❯ size test_rtti test_llvm_cast
text data bss dec hex filename
3577 832 8 4417 1141 test_rtti
3428 824 8 4260 10a4 test_llvm_cast
考察
コードのサイズ
コードの量としては、RTTIを利用したコードのほうが45%ほど小さいです。
これは、RTTIを利用していないため、enumやclassofの実装が必要になるためでした。
バイナリサイズ
バイナリサイズとしては両方ともに17KiBであり、ほぼ同じでした。
一方で、sizeコマンドのtextセクションつまり、実行コードはRTTIを利用しないほうが 5%ほど小さくなっていました。
dataセクションやbssセクションはほぼ同じでした。
これはdynamic_castの実行がそれだけオーバーヘッドがあることを示してると伺えます。
まとめ
RTTIを利用せず、LLVMで用意しているライブラリを利用したほうが実行コードサイズが小さくなることがわかりました。
一方、コードサイズ自体は大きくなる事がわかりました。