ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

lambdicSql - パフォーマンス改善せねば② -

※この書き方は最新のLambdicSqlとは異なります。速度計測の記録なので以前のままにしております。

やっぱ、昨日の仕様なし

前回コンパイルを減らすためにパラメータを渡すという仕様にしたのですが、やめます。
www.nuget.org

変数から値を取得するラムダを一回コンパイルしてキャッシュすることにしました。

これもキャッシュできました。こんな感じ。
ここではメンバ取得処理をインターフェイスでキャッシュしています。
Type Erasure っていう手法ですね。

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace LambdicSql.Inside
{
    static class ExpressionToObject
    {
        static Dictionary<string, IGetter> _memberGet = new Dictionary<string, IGetter>();

        internal static bool GetMemberObject(MemberExpression exp, out object 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)
            {
                return false;
            }

            //キャッシュの名前はこれで一意に決まるんじゃないかな。
            var getterName = constant.Type.FullName + "@" + string.Join("@", names.ToArray());
            IGetter getter;
            lock (_memberGet)
            {
                //ヒット!高速に値が取得できる
                if (_memberGet.TryGetValue(getterName, out getter))
                {
                    obj = getter.GetMemberObject(constant.Value);
                    return true;
                }
            }

            //最初の一回は仕方ない
            //パラメータは毎回変わるので、そこは引数で渡せるラムダを生成
            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);
            return true;
        }

        //ジェネリック型で生成してインターフェイスで型を消して使う
        interface IGetter
        {
            void Init(Expression exp, ParameterExpression param);
            object GetMemberObject(object target);
        }

        class GetterCore<T> : IGetter
        {
            Func<T, object> _func;

            public void Init(Expression exp, ParameterExpression param)
            {
                _func = Expression.Lambda<Func<T, object>>(exp, new[] { param }).Compile();
            }

            public object GetMemberObject(object target) => _func((T)target);
        }
    }
}

こんな感じでめでたく、普通に書くことができました。
フィールドやプロパティーから取得するのはこの手法が使えてキャッシュされるので速いですね。ただラムダ式にメソッド呼び出し入ると、今のところコンパイル入りますね。余裕があればそこも頑張ってみるかなー。

[TestMethod]
public void 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