LoginSignup
2
0

More than 5 years have passed since last update.

C++の仮想関数って、どれくらい遅いの?

Last updated at Posted at 2018-12-24

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 って書きたかったから。そんだけ。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0