C#のMicro-ORM(Dapper, Massive, PetaPoco)について

最近、巷という名の極一部で話題になっているMicro-ORMという分野。何それ?うーん、MicroなORマッパーです。まんまですが。Entity FrameworkがUltra Hugeだとしたら、その対極にあるような。定義としては「SQL本文は手書き、マッピングは自動」だけの機能を持つもの、といったところでしょうか。Microであるために「1ファイルのみ」を入れても良さそう。少なくとも、用語の発生元っぽいDapper, Massive, PetaPocoは1ファイルのみです。

他に、シンプルめなORマッパーを志している感じなのはSimple.Dataや、そしてORマッパーじゃなくてただの実行機です!と言い張る私の作っているDbExecutorなどもMicro-ORMに入れちゃってもいいかしら?Simple.Dataは名前に反してあまりシンプルではない(MEF使ってる……)感じですが。DbExecutorは十分Microです!id:taediumさんの作られているSomaも比較的シンプルめなものに入るかしら……?

この中で注目株はDapperで、Stack Overflowのエンジニアがパフォーマンスの問題を解消するために作成したもの、ということで、超巨大サイトでの利用で鍛えられているというのは何よりも信頼への担保がある。そして、この手のO/Rマッパーにはかかせない動的コード生成によるパフォーマンス向上に関しては、パフォーマンスのエキスパート中のエキスパートであるprotobuf-netの作者 Marc Gravell氏がIL生成部分を担当しているという、凄いコンビでそりゃ色々と叶わないね、という感。

パフォーマンステスト

Dapperは親切にもベンチマークプログラムも公開しているので、リポジトリにあるもの+DbExecutor、それとDataTableを追加して計測してみました。

hand coded took 53ms
Mapper Query (buffered) took 55ms
Dynamic Mapper Query (non-buffered) took 55ms
Mapper Query (non-buffered) took 56ms
Dynamic Mapper Query (buffered) took 56ms
Dapper.Cotrib took 56ms
PetaPoco (Fast) took 58ms
DbExecutor Select took 58ms
DbExecutor ExecuteReader(Hand Coded) took 58ms Dynamic Massive ORM Query took 62ms
PetaPoco (Normal) took 62ms
DbExecutor SelectDynamic took 62ms
DbExecutor ExecuteReaderDynamic(Hand Coded) 65ms
DataTable took 83ms
BLToolkit took 85ms
Simple.Data took 90ms
Linq 2 SQL Compiled took 100ms
SubSonic Coding Horror took 114ms
Entity framework CompiledQuery took 119ms
NHibernate SQL took 127ms
NHibernate HQL took 149ms
Soma took 168ms
NHibernate Criteria took 191ms
Linq 2 SQL ExecuteQuery took 215ms
Linq 2 SQL took 671ms
NHibernate LINQ took 708ms
Entity framework ExecuteStoreQuery took 726ms
Entity framework ESQL took 728ms
Entity framework No Tracking took 966ms
Entity framework took 969ms
SubSonic ActiveRecord.SingleOrDefault took 4259ms

hand codedがExecuteReaderを手で回した手書き、「Mapper Query」はDapperのことです。複数種類があるのはオプション違い。DbExecutor(太字にしています)も同様に4種類で測っています。

結果ですが、勿論Dapperは速いんですが、DbExecutorのSelectも悪くない位置にある。というか、これは普通に高速と名乗っていいレベルの速度は出てる。というか上位陣はほとんど誤差でいいんじゃないですかというところですね、実際何回か測ると若干入れ替わったりしますし。500回のループで3ms遅くて低速とか言われたら怒ります(笑)

ところでDbExecutorのDynamic類が十分すぎるほど速いのは少し驚いたり(dynamic経由だからもっとずっと遅くなるのかと思ってた……)。この計測結果を前にすると、手動マッピングするならExecuteReaderはダルいからExecuteReaderDynamic使いますねー、型変換とか不要でずっとシンプルに書けますから。Selectが使えるシーンではSelectで、柔軟なマッピングをする必要があるシーンではExecuteReaderDynamicで、というのがDbExecutorを使う場合の幸せシナリオになりそう。また、ExpandoObjectをDataTableのRowの代わりとして使うSelectDynamicも十分な速度が出ていて、これぐらい速度出るなら普通に使っちゃえますねえ。非常に良い感じ。

何故速いのか、あるいは何故遅いのか

動的コード生成しているから速い。といっても、真ん中ぐらいより上のコードはみんなやっているのではないかしら。勿論、DbExecutorも生成しています。では何でDbExecutorは大体の場合において僅差とはいえDapperより遅いのか。これはコード生成する範囲の問題です。Dapperはクエリ用に最適化してガッツシ固めて生成・キャッシュしてますが、DbExecutorは汎用的に、Typeに対して行っているので、そこで若干の差が出ています。

最初Dapperのコード見たときは、うわあ、これは凄い差がついちゃってるのでは?とか思ったんですが、蓋を開けてみると、誤差みたいに小さな差でたいしたことなかったので、このまんまで行きます(それと言い訳がましいですがCode Contractとかのハンデも若干あるので、どの程度響いているかは不明瞭ですが)。どちらにせよ、ようするところ、DBへのアクセス速度に比べれば、その程度のチューニングは大して差が出ないということでしょう。

動的コード生成も、ILでゴリゴリじゃなくてExpressionTreeを使ったゆとり全開の生成なので、それで上位にガッツリ肉薄しているのだから、十分以上です。

まとめ

DbExecutorは普通に速い。他のと比べると気が効いていて、かつ洗練されたAPIを持っていて、非常に使いやすいので、Dapperなどと比べても負けてない。Micro-ORMの最前線で全然戦えます(但し1ファイルではないけれど!)。次のアップデートでは、クエリ時の戻りが複数になる場合の対応と、ストアドプロシージャのOUTPUTの対応を予定してますので是非是非お試しを。

と、宣伝がましくなってて申し訳ないですね……。Micro-ORM自体については、まず、Entity Frameworkなど普通のORMを使わないようなら、必需品だと思います。完全手作業でデータベース触るのは馬鹿げてる。何らかの薄いラッパーぐらいは作っているだろうけれど、Dapperよりも優れていると確信持てなければ(そして多くの場合は持てるわけがない!)、そんなものは捨ててDapperを使ったほうがいいのではないかしら。いや、DbExecutorでもいいですけどね、というかDbExecutorは良いですよ。

Entity Frameworkなどを使っている場合はどうか、というと、重量級フレームワークの欠けた部分を補ってやる感じで、良い感じに使えそうです。その辺の小回りの良さ、大きめなものと一緒に使っても上手く馴染むのはMicro-ORMならではなのではかと思います。

Comment (4)

taedium : (06/04 08:42)

DbExecutor速いですね!hand codedと10ms程度しか変わらないとか、誤差ですね。

Somaも含めてもらってありがとうございます。これを見るとSomaが遅いことがよくわかります(^^; Somaはデフォルトでログをコンソールに出力してしまうので、もしデフォルトのまま使っているならMsSqlConfigの実装クラスで public override Action Logger { get { return SilentLogger; } } としてもらえるとうれしいです。すでにそうしてもらっているならば余計なこと言ってすみません。

Dynamic類が十分すぎるほど速いのは、テストケースとも関係しているのかなと思います。Dapperのベンチマークプログラムでは、Dynamicなオブジェクトのプロパティやメソッドが呼び出されることがないので。

それと、速度に関して動的コード生成以外の要因で大きいものとしては型変換の処理があるかもしれませんね。ここでの型変換とは、たとえば、IDataReaderはintを返すけどマッピング先のプロパティはlongやstringなので型を変換するといったものです。Somaは型変換処理をしますが、型変換処理をベンチマークに含めないようにすると(SomaではDynamic型を使うことで型変換処理を遅延できます)いいパフォーマンスがでました。とは言っても上位陣にはぜんぜん追いつきませんでしたけど。

neuecc : (06/05 04:16)

どうもありがとうございます。

Somaですが既にDapperのリポジトリに取り込まれているので、それでテストしてしまいました。
日記はいつも読ませていただいていて、パフォーマンスが日々改善されているのも見ていたので、
横着せず新しい版で、きちんと調整して試せばよかったですね、あとで再度測ってみます。
リポジトリへはpull requestも受け付けているようなので、出したほうがいいかも。
私もDbExecutorの次版が出来たらベンチに含めて貰うよう出すつもりです。

テストケースですが、
Id, Text, CreationDate, LastChangeDate, Counter1~9と、
プロパティ13個にアクセスしていることを考えると、割と満たされているかな?と思いましたがどうでしょう。

あ、型変換というか、むしろParseですか!
なるほど、それはあると凄くいいです。
というか現実的には必需品だと過ぎるシチュエーションが色々いっぱい。
(何故か片っぱしからnvarcharになっていてintにしなければ、とか多発でして……)
パフォーマンスの話含め、とても参考になります。

taedium : (06/05 11:27)

> Somaですが既にDapperのリポジトリに取り込まれているので、それでテストしてしまいました。

えっ?取り込まれている?そうなんですね、知りませんでした。わざわざ取り入れて計測してもらったものとばかり思っていました。確認せずに言ってしまってすみません。ちょっと古めのバージョンが取り込まれているようなので、私もpull requestします(1.0.0.0をリリースしたらにしようと思ってますが)。

> プロパティ13個にアクセスしていることを考えると

テストケースではdynamicなオブジェクトのメンバから値を取り出すようになっていないように見えます。たとえば、Massiveの例でいいますと、次のようなコードはないですよね?。
dynamic post = massiveModel.Query(…).First();
var text = post.Text; // ここ!
微々たるものだとは思いますが、静的に型が決まっているほうがdynamicより速くなると考えています。

> あ、型変換というか、むしろParseですか!

Parseも含まれますが、System.Convert.ChangeType()を意識して型変換と書きました。

こちらこそneueccさんのブログは参考にさせてもらっています。Somaにdynamicを取り入れたのはneueccさんのエントリを読んで刺激を受けたからなんです。

neuecc : (06/06 21:30)

> テストケースではdynamicなオブジェクトのメンバから値を取り出すようになっていないように見えます
なるほど。
DbExecutorの話で恐縮ですが、ExecuteReaderDynamicでは、

.ExecuteReaderDynamic(@”select…})
.Select(reader => new Post
{
Id = reader.Id,
Text = reader.Text,

})
.ToList();

といった形で使い、あくまでHand Codedのプロパティ名と型変換の簡略化を目的にしていて
列挙が回った時には型を持ったオブジェクトに変換されているので、こちらは完全に計測されています。

もう一つのSelectDynamicでは
ExpandoObjectに変換しているため(MassiveのdynamicなQueryも同じですね)
確かに値の取り出し時のコストの問題が出てくるのですが
ただ、それはExpandoObjectの話になるので、ORMのパフォーマンスとは別の話かな、と思います。

> Parseも含まれますが、System.Convert.ChangeType()を意識して型変換と書きました。

あ、dynamicはint->longのような暗黙的な型変換はサポートしていますが
int->shortのような明示的な変換が必要なものはダメなんですね。
その辺がちょっと曖昧のままでしたので、話がParseに飛躍しました、すみません。

Name
WebSite(option)
Comment

Trackback(0) | http://neue.cc/2011/06/03_326.html/trackback

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for Visual Studio and Development Technologies(C#)

April 2011
|
July 2019

Twitter:@neuecc
GitHub:neuecc
ils@neue.cc