Ktouth Brand. on Web

け〜くんこと K.Ktouth のだらだらした日常と突発的に作るプログラムや読み物とかの雑多サイト



[2012年02月07日]

拡張メソッド構文の方が高速か?

2012年02月07日 21:13更新 筆者:K.Ktouth

先日のコードを書き換えてみたのですが。ちょっと意外な事実が判明。
C# の Linq はクエリ構文で書くより、拡張メソッド構文で書く方が実行速度が速い可能性がある模様。もちろん今回のコードに限った話なのかも知れませんが、Linq to SQL/Entities は実際のコードから構築された式木を T-SQL 構文に変換するという処理を行っていますから、クエリ構文だと構築される式木がやや複雑化するとか、そういうことなのかも。

で、なんでわざわざ拡張メソッド構文にしてみようかと考えたかというと、Entitiesの制限を超えるために動的クエリ生成に挑戦してみようと思って、まずその前段階としてやってみたわけですな。
とりあえずここまではただの書式変換でしかないのでいいとして、問題は動的クエリ生成、か……

(以下、コード)

まず、これが前回書いたコード。

public static IQueryable<Sample> DateFirst(this IQueryable<Sample> table, DateTime time)
{
  var tDates = from _b in table
         where _b.Date <= time
         group _b by _b.GroupId into _gb
         select new { GroupId = _gb.Key, Date = _gb.Max(m => m.Date) };
  var tIds = from _b in table
        join _d in tDates on _b.GroupId equals _d.GroupId
        where _b.Date == _d.Date
        group _b by _b.GroupId into _gb
        select new { Id = _gb.Max(m => m.Id) };
  return from i in table
      join _i in tIds on i.Id equals _i.Id
      where i.IsVisible
      select i;
}

で、調べてみたら、join 句で複合キーの指定方法がわかったので試してみました。

public static IQueryable<Sample> DateFirst_B(this IQueryable<Sample> table, DateTime time)
{
  var tDates = from _b in table
         where _b.Date <= time
         group _b by _b.GroupId into _gb
         select new { GroupId = _gb.Key, Date = _gb.Max(m => m.Date) };
  var tIds = from _b in table
        join _d in tDates on new { _b.GroupId, _b.Date } equals new { _d.GroupId, _d.Date }
        group _b by _b.GroupId into _gb
        select new { Id = _gb.Max(m => m.Id) };
  return from i in table
      join _i in tIds on i.Id equals _i.Id
      where i.IsVisible
      select i;
}

結果はわずかに処理が重くなった模様。1%前後なので誤差程度ですが。
で、これをさらに拡張メソッド構文に書き換え。

public static IQueryable<Sample> DateFirst_C(this IQueryable<Sample> table, DateTime time)
{
  var tDates = table.Where(_b => _b.Date <= time)
           .GroupBy(_b => _b.GroupId)
           .Select(_gb => new { GroupId = _gb.Key, Date = _gb.Max(m => m.Date) });
  var tIds = table.Join(tDates, _b => new { _b.GroupId, _b.Date }, _d => new { _d.GroupId, _d.Date }, (_b, _d) => _b)
          .GroupBy(_b => _b.GroupId)
          .Select(_gb => new { Id = _gb.Max(m => m.Id) });
  return table.Join(tIds, i => i.Id, _i => _i.Id, (i, _i) => i)
        .Where(i => i.IsVisible);
}

結果は明らかに処理が軽くなりました。初期のコードの3割ほど減っているので、誤差ではなく、全体的に最適化が進んでいると思われます。

public static IQueryable<Sample> DateFirst_C2(this IQueryable<Sample> table, DateTime time)
{
  var tDates = table.Where(_b => _b.Date <= time)
           .GroupBy(_b => _b.GroupId)
           .Select(_gb => new { GroupId = _gb.Key, Date = _gb.Max(m => m.Date) });
  var tIds = table.Join(tDates, _b => new { _b.GroupId, _b.Date }, _d => new { _d.GroupId, _d.Date }, (_b, _d) => _b)
          .GroupBy(_b => _b.GroupId)
          .Select(_gb => _gb.Max(m => m.Id));
  return table.Join(tIds, i => i.Id, _i => _i, (i, _i) => i)
        .Where(i => i.IsVisible);
}

で、最後は一部分のコードを単純化。わずかに軽くなりましたが、これも1%程度の誤差レベル。
結果としては何度も繰り返し使うようなクエリ、処理が重くなりそうな大きなクエリは拡張メソッドで書くと、心持ち早くなることが期待出来そうです♪

本日のリンク元
その他のリンク元
検索