- originally written at 2003.Oct.27
- subtly modified at 2003.Oct.28
スマートポインタ考察 II
前回考察Iの終わり方からして、やっぱりと思う方もいらっしゃるかと思う。
私自身もそうだ。IIを書くことにしたのは必然かもしれない。
今回はなんとなく、拙作スマートポインタの設計理念というか、BoostやLoki
ライブラリに対する苦し紛れの言い訳を書き連ねるための1ページのような気がするが、
その辺はご容赦いただきたい。そんなのは読みたくないという人はこの時点で
別のページへ飛んでくだされ。さてさて、続きの話を。
(Iを読んでいない人は先にそちらをどうぞ。)
さて、Boostのshared_ptrの機能はかなりよさそうに見えるが、前回書いた
ような理由で、完璧とは言えないと思う。resetやgetが付いているのは
auto_ptrとのコンパチを考えたせいかそうでないのかよく知らないが、
あまり好ましくない。まあ、そのせいで落第するほど点数は下がらないけど。
十分使えるので、使ったことがない人は試してみるとよいと思う。
こんなのも書いてくれてるし。
LokiのSmartPtrは、古いコンパイラを使ってる人にはツライ
(これはLoki全般に言える話だが)。コンパイルが通らないのでは話にならないから。
テンプレートをガンガン使ってスマートポインタのありとあらゆる機能を
ポリシー化しているので、カスタマイズが幅広いと言えば広いのだけど、
そこまでして使ってる人がいるのかどうか。定義済みのポリシーで入れ替える
くらいはやっている人もいるかもしれないが、それでも面倒くさいのは確かである。
それにもまして、自分でポリシーを定義するとなると、スマートポインタ自体を
自分で書いたほうがいいのでは、くらいの気になってくる。
あんまりテンプレート多用してパラメータ化してるのは個人的に好きではない。
そうするなら定義済みポリシーを適用した典型パタンのエイリアス版を作っておいて
ほしいと思うのは私だけか。
さて、槍玉に上がった二者だが、いろいろ言う前にこれらが
優れたスマートポインタであることを前提として述べておく。洗練されている
ことに違いはないし、うまくやったものだと感心するような実装もある。しかし、
欠点がまったくないわけではないのも事実である。
スマートポインタを使う上で、速度はかなり重要である。
生のポインタのところをわざわざスマートポインタに変えるわけだから、
オーバーヘッドがどれくらい小さいかは勝負どころだろう。この点に関して、
LokiのSmartPtrはあまりよろしくない。パラメータ化によって速度を犠牲にした
感すらある。特にRefLinkedを使ったときは、なぜそんなに遅くなるのかと
言いたいほど差が付く。これの使用はおすすめしない。Boostのshared_ptrに関しては、
問題外である。遅い。デフォルトのまま使っていては日が暮れるかもしれん
(これは大げさすぎるかな)。これについてBoost関係の人たちも頭を使っているようで、
別の実装方法をいろいろ検討されている。
詳しくはこちらを参照されたい
(
日本語はこちら)。
最近のshared_ptrはマクロでアロケータが切り替えれるようになっているので、std::allocator
やquick_allocatorを使えば多少速くなるだろう。これはこの検討の成果だろうか。
でもリンクリスト型はまだ(2003年10月27日現在は)正式にはBoostにない。
BoostやLokiのそれが遅いのは、余計なものまでアロケートしているからだ
と言えなくもない。余計なものとは、必要最小限以上の機能を提供するために追加されたデータメンバ
のことである。shared_ptrはweak_ptrをサポートするためだと思うが、カウンタを2つ使っているし、
内部にポインタをコピー保持していたり、ほかにもメンバを持っている。
これはいわゆるトレードオフである。このあたりの判断は難しいところだが、機能を増やすにも
何段階かのモードがほしい。そうなるとやはりLokiみたいにパラメータ化するのがよいのだろうか。
しかし、Lokiがなぜ遅いのか不思議ではある。テスト環境(私はVC7)に依存するのかもしれない。
拙作のスマートポインタでは、専用のアロケータを用いた参照カウント型と
リンクリスト型の2種類がある。どちらも前述二者と比較すると速い。それは単に、
シンプルな実装で余計な機能を付けていないからかもしれない。
拙作スマートポインタはauto_ptrのことなど全く考えていない。
そいういうコンパチが欲しい人は、継承して自分の好きな名前のメソッドを
作ってくれればいい。拙作スマートポインタは第一にWin32用であり、WTL/ATLと同時に使うことを
目的としている。そういう意味ではATL::CSharedPtrと言えるだろうし、
将来そのように名称を変更するかもしれない(勝手に人の名前空間を使うな、
とお怒りになる方もいるかもしれないが)。速けりゃいいのか、weak_ptrや複雑なポリシー化のような
高度な機能がいいのか、それは状況次第である。しかし、少なくとも私は
(日曜プログラマの経験の少なさ故かもしれないが)weak_ptrをモノに使ったことがない。
猫に小判である。
- additionally written at 2004.Mar.18
遅い遅い、ウチのは速い、と言っているだけでは根も葉もないので、
データを提示しておくことにした。
ここにあるテスト用コードを少々改造して同様のテストをしてみた。
改造後のソースコードは
これ。VC7でビルドした。
コンパイルオプションは
/O2 /Ob1 /Oy /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /GF /FD /EHsc /ML /GS /Gy /Fo"Release/" /Fd"Release/vc70.pdb" /W3 /nologo /c /Wp64 /Zi /TP
である(コピペですみません)。テスト実行環境はWinXPProSP1、Pen3M800MHz。
結果のcsvを
こちらにおいておく。
csvファイルが3つ入っているが、
smartsst.csvが普通にlibc.libで(上記コンパイルオプション)ビルドしたときの結果、
smartsmt.csvがlibcmt.libにしてビルドしたときの結果、
smartsstx4.csvがシングルスレッド版をテスト回数4倍にして実行したときの結果である。
環境によって全然違うと思うので、気になる人は自分で試してください。
数値を見ればわかるのだが、とりあえず一つだけグラフ化した(smartsst.csv)。
縦軸:時間(ns)、横軸:1セット中のスマートポインタの数(n_inset)。
上記リンク先のサイトと結果のパターンが全然違うのだが、私が何か間違っているのだろうか?
まあ、気にせず話を進めよう。
いくつか(50ns以上のもの)を除いて、どれもいい勝負。ナノ秒単位の戦いをしているし、
その辺で競っているやつらはテスト毎にめまぐるしく順位が変わるので、ほぼ同等と言ってよい。
(smartsstx4.csvの方ではもっと安定した結果が出るので、微妙な違いはそちらで検討すべし。)
グラフが見にくいかと思うが、このような理由で問題はそこではないので、勘弁してほしい。
イヤだという方は、CSVをDLして自分でグラフ描いてください。
最高峰を描いてくれるハナシにならない緑のグラフが、Boostの標準のshared_ptrである(BstShrd)。
これでスピードが問題にされる理由がおわかりいただけるだろう。
途中でshared_ptrを抜いてワースト1に輝いている青いグラフが、
LokiのSmartPtrでRefLinkedのものである(LokiLnk)。
もう一つの青いグラフも通常のLoki::SmartPtrのものであり(LokiCnt)、
Lokiは遅いと私が言う根拠がこれである。
Boost::shared_ptrほどではないが高いところにある2つの赤いグラフは、
拙作スマートポインタ(参照カウント型)でアロケータに普通にnew/deleteを使ったとき(AtzECNew)と、
std::allocatorを使ったとき(AtzECStd)のものである。
これらはできれば使わないほうがよい。
それ以外にも参照カウント型の拙作物としては、
デフォルトの専用アロケータ(AtzCnt、AtzExCnt)、
直接ヒープAPIを使うアロケータ(AtzECW32)、Boostのquick_allocator(AtzECQck)、
をそれぞれ使うものがレースにエントリーしているが、
私の環境でテストしたところではAtzECW32が一番速いので、
Windowsアプリでマルチスレッドであればこれを使うのをお勧めする。
また、拙作のリンクリスト型(AtzLnk、AtzExLnk)は、他のものより速い。
リンクリスト型は初速がよいので、私は通常これを使っている。グラフの右のほうへ行けば、
リンクリスト型と参照カウント型の違いが出てくるのだが、
私は同じオブジェクトが100箇所で共有されるようなプログラムを作ることがないので、
ほとんど参照カウント型を使うチャンスがない。
しかし、Boostのレポートにもあるとおり、カウント埋め込み型が一番速い。
これは当然である。比較するのは無意味だとすら言える。
参照カウント型のネックはカウントのアロケートにかかる時間にあるのだが、
このタイプはそのアロケートコストをテスト対象(計測範囲)から外しているだけだ。
要するにズルい。まあ、このタイプが適用できるならそうしたほうがよいだろうが。
拙作物でもポリシーを入れ替えてこのタイプを作れるようにはなっている。
でも、COMを使うときは私はCComPtrを使うので、ほとんど出番はないだろう。