※この書き方は最新のLambdicSqlとは異なります。速度計測の記録なので以前のままにしております。
やっぱ、昨日の仕様なし
前回コンパイルを減らすためにパラメータを渡すという仕様にしたのですが、やめます。
www.nuget.org
変数から値を取得するラムダを一回コンパイルしてキャッシュすることにしました。
これもキャッシュできました。こんな感じ。
ここではメンバ取得処理をインターフェイスでキャッシュしています。
Type Erasure っていう手法ですね。
using System; using System.Collections.Generic; using System.Linq.Expressions; namespace LambdicSql.Inside { staticclass ExpressionToObject { static Dictionary<string, IGetter> _memberGet = new Dictionary<string, IGetter>(); internalstaticbool GetMemberObject(MemberExpression exp, outobject obj) { //ConstantExpressionを探す obj = null; var member = exp; var names = new List<string>(); ConstantExpression constant = null; while (member != null) { names.Add(member.Member.Name); constant = member.Expression as ConstantExpression; if (constant != null) { break; } member = member.Expression as MemberExpression; } if (constant == null) { returnfalse; } //キャッシュの名前はこれで一意に決まるんじゃないかな。 var getterName = constant.Type.FullName + "@" + string.Join("@", names.ToArray()); IGetter getter; lock (_memberGet) { //ヒット!高速に値が取得できるif (_memberGet.TryGetValue(getterName, out getter)) { obj = getter.GetMemberObject(constant.Value); returntrue; } } //最初の一回は仕方ない//パラメータは毎回変わるので、そこは引数で渡せるラムダを生成 var param = Expression.Parameter(constant.Type, "param"); Expression target = param; names.Reverse(); names.ForEach(e => target = Expression.PropertyOrField(target, e)); getter = Activator.CreateInstance(typeof(GetterCore<>).MakeGenericType(constant.Type), true) as IGetter; getter.Init(Expression.Convert(target, typeof(object)), param); lock (_memberGet) { if (!_memberGet.ContainsKey(getterName)) { _memberGet.Add(getterName, getter); } } obj = getter.GetMemberObject(constant.Value); returntrue; } //ジェネリック型で生成してインターフェイスで型を消して使うinterface IGetter { void Init(Expression exp, ParameterExpression param); object GetMemberObject(object target); } class GetterCore<T> : IGetter { Func<T, object> _func; publicvoid Init(Expression exp, ParameterExpression param) { _func = Expression.Lambda<Func<T, object>>(exp, new[] { param }).Compile(); } publicobject GetMemberObject(object target) => _func((T)target); } } }
こんな感じでめでたく、普通に書くことができました。
フィールドやプロパティーから取得するのはこの手法が使えてキャッシュされるので速いですね。ただラムダ式にメソッド呼び出し入ると、今のところコンパイル入りますね。余裕があればそこも頑張ってみるかなー。
[TestMethod] publicvoid CheckLambdicSqlCondition() { int x = 1; var times = new List<double>(); using (var connection = new SqlConnection(TestEnvironment.ConnectionString)) { connection.Open(); for (int i = 0; i < 10; i++) { var watch = new Stopwatch(); watch.Start(); var datas = Sql.Query<DB>().SelectFrom(db => db.TableValues). Where(db => db.TableValues.IntVal == x).ToExecutor(connection).Read().ToList(); watch.Stop(); times.Add(watch.Elapsed.TotalMilliseconds); } } ShowTime(times); }
キャッシュされてるから二回目以降は速いですね。
ちょっと変なの混じるからコンソールアプリに変えて100回の平均を出してみました。
0.15ミリ差がありますね。
もう少し細かく見てみよっと。
(msec) | LambdicSql | Dapper |
---|---|---|
1 | 1.4219 | 2.1768 |
2 | 0.9891 | 1.088 |
3 | 0.7876 | 0.8783 |
4 | 0.7347 | 0.6551 |
5 | 0.729 | 0.6662 |
6 | 2.3499 | 0.6391 |
… | … | … |
… | … | … |
100 | 0.7068 | 0.8274 |
平均 | 0.908061616 | 0.758524242 |