プログラミング

【速度実験】vectorのpush_back()とemplace_back()はなにが違うのか?

C++_vector_速度

はじめに

久しぶりの速度実験です.

今回も,C++のvectorについての調査実験です.

前回は,vectorのメモリ確保についての実験でした.

  1. 【関連記事】C++速度実験その2!vector配列の扱いについて

これが,意外と検索結果の上位に表示されているみたいで,多くの方々に閲覧されています.

稚拙な内容と文で,恥ずかしい思いと嬉しい思いが混ざっていますが,そんなワケで続編をやろう!となりました.

なお,本記事では小難しい話はしません,というか書いている私がまだ深く理解できていないので,下手なことを書くと誤解が生まれちゃうので...

push_back()とemplace_back()

さて本題です.

C++のvectorクラスは,C++利用者にとっては無視できないとても便利なコンテナクラスです.

他にも配列として扱えるクラスはありますが,とりあえずこれ使っとけっていうクラスです.

が,要素を格納する関数が2種類あります.

そう,push_back()emplace_back()です.

emplace_back()はC++11から追加された関数です.

結果としては,同じ「要素を格納する関数」なのですが,何が違うのでしょうか?

コピーとムーブ

それは,要素をvectorに渡すときに生じます.

push_back()の場合,引数として受け取ったものをコピーするために一時的なオブジェクト生成がなされるようで,その際その引数のコピーコンストラクタデストラクタが逐一呼ばれるようです.(あってるのかな)

が,それは無駄ということで,emplace_back()が登場しました.

emplace_back()は,引数として受け取ったもののクラスのコンストラクタ引数から,直接コンストラクトするため無駄がないとか.

...うーん,ちょっと複雑.

よくある話

push_back()とemplace_back()の話題は結構あって,検索するとたくさん出てきます.

そして結論として,「とりあえずemplace_back()を使っとけ」となっている記事が多いですね.

それを信じて私もそうしていましたが,どの程度速くなるのかは気になりますよね.

それでは,早速実験してみましょう.

実験条件

まずは共通の実験条件です.

コード全体は最後に載せますが,実験解説のときに使用するコードはその一部になります.

実験マシンスペック

OSMacOS 10.15.3 (macbook pro 13 2018)
CPUIntel(R) Core(TM) i7-8559U CPU @ 2.70GHz (4 cores)
RAM16 GB

計測

時間の計測はchronoを使用してミリ秒単位で計測したのち,秒単位に直して観察します.

描画

今回は実験結果を比較しやすいようにグラフ描画を行いますが,その際matplotlibcppというライブラリを使用します.

PythonのmatplotlibをC++で使えるようにしてくれたヘッダオンリライブラリです.

ちょっと環境構築が面倒ですがC++でグラフ描画するなら,今のところ一番便利かな?

ソースは以下に載せておきます.

lava/matplotlib-cpp: Extremely simple yet powerful header-only C++ plotting library built on the popular matplotlib

実験1 : double型

まずは64bit浮動小数点型から.

この二つを,サイズ1万~100万までを1万刻みで与え,10試行平均を観察します.

スポンサードリンク




実験結果1

C++_vector_速度実験

そんなに差は出ませんでしたね.

push_back()の方は,小さなデータサイズでかなり時間がかかっていますが,なぜなんでしょうね?

ちょっとわかりません,何かしらのオーバーヘッドでしょうか...

実験2 : 小さな適当Class

次は自分で作った適当なクラスを格納してみます.

今回はこんな適当クラスを用意してみました.

小さなクラスですが,どうなるでしょうか.

あ,ちなみにクラスを格納するときに, emplace_back(Sample(0.1, 0.1)) とやったらpush_back()と同じです.

emplace_back(0.1, 0.1) のようにコンストラクタの引数を渡すことでemplace_back()の良さが出るそう.

ちなみにサイズや試行回数は先ほどと同じです.

実験結果2

C++_vector_速度実験

ややemplace_back()の方が速い...かな?

微妙ですね,そんなに大きな差ではなさそうです.(有意差は面倒なので調べてません)

まあそれもそのはず,大事なのはコピーのときに大きな計算コストがかかるかどうか,です.

今回はコピーコンストラクタを特に実装していなかったので,このような結果になったのしょう.

(push_back()の小さいサイズでの低速現象は,依然としてありますが,もう無視しましょう)

実験3 : コピーコンストラクタが重めのクラス

というワケで実装しなおしてみましょう.

クラスは以下のように改変.

ちょっとワケあって脳筋な実装だけど,よしとします.

実験結果3

C++_vector_速度実験

ここで結構差が出ました.

やはり,push_back()とemplace_back()の大きな差はコピーですね.

でも,この結果から単純にemplace_back()の方が優れていると結論づけて良いのかどうか...

補足

ちなみに emplace_back(Sample(0.1, 0.1)) で実験すると,以下のようにどちらもコピーコンストラクタが呼ばれてしまい差は出ません.

C++_vector_速度実験

実験4 : 文字列を要素とする場合

最後は文字列(string)クラスでやってみます.

なぜなら,よく使うクラスの中ではコピーコンストラクタが比較的重そうなクラスだから.

ちなみにstringのコピーコンストラクタでは,メモリ領域確保に加え,forループによるchar配列コピーが行われているようです.

(↓参考: C/C++ ポインタ入門 > 文字列クラス > コピーコンストラクタ)

実験結果4

c++_vector_string

やはり結果に差が出ましたね.

stringを格納するならばemplace_back()の方が速いです.

まとめ

今回はvectorのpush_back()とemplace_back()の比較実験を行いました.

結論としては「とりあえずemplace_back()使っとけ」は間違ってはなさそうです.

特にコピーコンストラクタが重めのものはemplace_back()が力を発揮します

それより,データサイズが小さいときにpush_back()が遅いのはなぜ??

実験に使用したコード

main.cpp

CMakeLists.txt

matplotlibcppを使用する場合のCmakeLists.txtは以下のように書く必要があります.

適当に参考にしてください.

おしまい.


スポンサードリンク