Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

Nabetani
横浜へなちょこプログラミング勉強会をやっていました。 / CodeIQ の出題者でした。 / 日経 WinPC に連載を持っていました(名義が違うけど) / Yokohama rb に半分ぐらい参加しています。 / twitter : http://twitter.com/Nabetani
https://nabetani.hatenadiary.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away