C++で学ぶ!メモリレイアウトとvtableのすゝめ 〜動的ポリモフィズムを実現する仕組み〜
を読んで、どんだけ遅いのかが気になったので測ってみた。
まずは、仮想だったり仮想じゃなかったりする関数を呼ばれるクラスたち:
vfunc_classes.h(c++14)
struct base
{
virtual void vfunc() const =0;
void non_vfunc() const;
virtual ~base(){}
};
struct derived1 : public base
{
virtual void vfunc() const;
virtual ~derived1(){}
static void func(base const &);
};
struct derived2 : public base
{
virtual void vfunc() const;
virtual ~derived2(){}
static void func(base const &);
};
そして、関数定義
vfunc_classes.cpp(c++14)
#include "vfunc_classes.h"
void base::non_vfunc() const {}
void derived1::vfunc() const {}
void derived2::vfunc() const {}
void derived1::func(base const &){}
void derived2::func(base const &){}
最後に、時間を測るひと。
vfunc.cpp(c++14)
// CC -std=c++14 -O2 vfunc.cpp vfunc_classes.cpp
#include <iostream>
#include <chrono>
#include "vfunc_classes.h"
constexpr int TRIAL = 1'000'000'000;
void vfunc(base const & b)
{
auto start = std::chrono::high_resolution_clock::now();
for( int i=0 ; i<TRIAL ; ++i ){
b.vfunc();
}
auto end = std::chrono::high_resolution_clock::now();
std::cout
<< "vfunc: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() * 1e-3
<< "ms"
<< std::endl;
}
void funcptr(base const & b, void(*func)(base const &b))
{
auto start = std::chrono::high_resolution_clock::now();
for( int i=0 ; i<TRIAL ; ++i ){
func(b);
}
auto end = std::chrono::high_resolution_clock::now();
std::cout
<< "funcptr: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() * 1e-3
<< "ms"
<< std::endl;
}
void non_vfunc(base const & b)
{
auto start = std::chrono::high_resolution_clock::now();
for( int i=0 ; i<TRIAL ; ++i ){
b.non_vfunc();
}
auto end = std::chrono::high_resolution_clock::now();
std::cout
<< "non_vfunc: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() * 1e-3
<< "ms"
<< std::endl;
}
int main()
{
derived1 d1;
derived2 d2;
vfunc(d1);
non_vfunc(d1);
funcptr(d1, derived1::func);
vfunc(d2);
non_vfunc(d2);
funcptr(d1, derived2::func);
return 0;
}
g++-8 | clang++ | |
---|---|---|
vfunc(d1) | 1511.62ms | 1791.4ms |
non_vfunc(d1) | 1245.71ms | 1671.74ms |
funcptr(d1,略) | 1502.68ms | 1651.72ms |
vfunc(d2) | 1514.37ms | 1787.41ms |
non_vfunc(d2) | 1267.34ms | 1707.33ms |
funcptr(d2,略) | 1495.54ms | 1654.25ms |
g++ も clang++ も、測定できるほどの差がある。
比率としては、g++ で 20%、clang++ で 5% ほど。ループを回すのに必要なコストも入っていることに注意。
g++ の場合は 仮想関数と関数へのポインタは同じぐらいの時間。
しかし意外にも。clang++ の場合、仮想関数の方が関数へのポインタより遅い。そういうもんか。アセンブラ見たほうがいいかなぁ。
いずれにせよ。
- 仮想関数でなくても良い関数は、仮想にすべきではない。
という原則は守ったほうが良い。
蛇足。C++14 になっているのは、1'000'000'000
って書きたかったから。そんだけ。