2010年1月2日土曜日

Expression生成のスピード?

いけてるINotifyPropertyChangedの実装は、結構遅かった - かずきのBlog@Hatena

正月から興味深いエントリですね。

遅くなるのは毎回のCompileと引数に渡してるExpression生成ですかね。Compileはキャッシュしてしまえば毎回生成する必要は無いですが、そもそも以下のようにすることでCompileそのものが不要に。

    public static void Raise2<TResult>(this PropertyChangedEventHandler _this,
      Expression<Func<TResult>> propertyName)
    {
      // ハンドラに何も登録されていない場合は何もしない
      if (_this == null) return;

      // ラムダ式のBodyを取得する。MemberExpressionじゃなかったら駄目
      var memberEx = propertyName.Body as MemberExpression;
      if (memberEx == null) throw new ArgumentException();

      // () => NameのNameの部分の左側に暗黙的に存在しているオブジェクトを取得する式をゲット
      var senderExpression = memberEx.Expression as ConstantExpression;
      // ConstraintExpressionじゃないと駄目
      if (senderExpression == null) throw new ArgumentException();

      var sender = senderExpression.Value;

      // 下準備が出来たので、イベント発行!!
      _this(sender, new PropertyChangedEventArgs(memberEx.Member.Name));
    }

こうすると、実行時間は...。

kairyo

ノーマル:5ms
イケテル:3700ms
カイリョ:84ms

※平均値じゃないですが。

これを更に高速化しようとイロイロと試してみたんですが、どうやらExpression<Func<T>>の生成に時間を取られる模様。

Raiseを呼び出さずにローカル変数として

Expression<Func<string>> expr = () => Name;

と、しただけで実行時間はほぼ同じくらいかかるし。全く素敵じゃなくなるけど、↓こんな感じにしておくとちゃんと早い。

  public class KairyoEmp : INotifyPropertyChanged
  {
    private string _name;
    private static Expression<Func<string>> _expr;
    public string Name
    {
      get { return _name; }
      set
      {
        _name = value;
        if (_expr==null)
          _expr = () => Name;
        PropertyChanged.Raise2(_expr);
      }
    }

    public event PropertyChangedEventHandler PropertyChanged;
  }

kairyo2

ノーマル:5ms
カイリョ:10ms

うむ。意味のないコードになってしまいました。年始からこれじゃ先が思いやられる...。