先日の日記の続き。
C++0x の高機能スマート ポインタ std::shared_ptr の構築/破棄処理の速度を計測してみました。VC2010 でコンパイル オプションは /O2 /EHsc /DNDEBUG。相対評価なんで PC スペックは割愛。
#include <memory> #include <vector> #include <iostream> #include <ctime> #include <algorithm> namespace { struct test { int array[32]; test(){} }; const std::size_t length = 1000 * 10000; }; int main() { std::vector<std::shared_ptr<test>> array; std::vector<test*> array_row; array.reserve(length); array_row.reserve(length); // std::shared_ptr のコンストラクタを使用 { const auto prev = std::clock(); for (std::size_t ct = 0; ct < length; ct++) { std::shared_ptr<test> ptr(new test); array.push_back(ptr); } array.clear(); std::cout << "ctor: " << static_cast<double>(std::clock() - prev) / CLOCKS_PER_SEC << "(s)" << std::endl; } // std::make_shared を使用 { const auto prev = std::clock(); for (std::size_t ct = 0; ct < length; ct++) { array.push_back(std::make_shared<test>()); } array.clear(); std::cout << "std::make_shared: " << static_cast<double>(std::clock() - prev) / CLOCKS_PER_SEC << "(s)" << std::endl; } // std::allocate_shared を使用 { const auto prev = std::clock(); std::allocator<test> alloc; for (std::size_t ct = 0; ct < length; ct++) { array.push_back(std::allocate_shared<test>(alloc)); } array.clear(); std::cout << "std::allocate_shared: " << static_cast<double>(std::clock() - prev) / CLOCKS_PER_SEC << "(s)" << std::endl; } // 生ポインタを使用 { const auto prev = std::clock(); for (std::size_t ct = 0; ct < length; ct++) { array_row.push_back(new test()); } std::for_each(array_row.begin(), array_row.end(), [](test* ptr) { delete ptr; }); array.clear(); std::cout << "ptr: " << static_cast<double>(std::clock() - prev) / CLOCKS_PER_SEC << "(s)" << std::endl; } return 0; }
上記コードでは 1000 万回の構築と破棄を計測しています。結果は下記*1のとおり。
ctor: 2.324(s) std::make_shared: 1.498(s) std::allocate_shared: 1.497(s) ptr: 1.201(s)
うわぁ。こうやって見てみると、生ポインタと比べて、単純に std::shared_ptr コンストラクタを使用したケースはオーバーヘッド大きいですね。管理対象インスタンス以外にデリーター類を別途動的確保しているだけはあります。
んー。std::make_shared については先日の日記での予想通り、単純にコンストラクタ使うより速いですね。生ポインタとの速度差もかなり少なくなっています。デリーター類を管理対象インスタンスと一緒にまとめて動的確保しているのが効いているんでしょう。この機構考えた人、頭良いですね。
また、std::allocate_shared も std::make_shared と同程度の速度になっていますね。アロケータが指定できる分、高速なアロケータを用意できれば std::make_shared std::allocate_shared の方が有利になりそうです。この辺、追加検証したいなぁ。
*1:各ケースの処理順序を入れ替えても速度傾向に変動は見られませんでした。