Archive for the ‘技術情報’ Category
[C#] LINQ to SQL の実行速度を3倍に加速するっ!
どーも最近、ダメな子扱いされがちな LINQ to SQL ですが、クエリ結果を(ほぼ)透過的にキャッシュすることで実行速度を約3倍に加速してみました。
使い方はひじょーにカンタン。
通常
var connectionString = Properties.Settings.Default.Database1ConnectionString;
using (var db = new DataClasses1DataContext(connectionString))
{
var array = db.Products.Where(__ => __.Price > 500).OrderBy(_ => _.Price).Take(200).ToArray();
}
みたいに書くところを
var connectionString = Properties.Settings.Default.Database1ConnectionString;
using (var connection = new Mjollnir.Data.SqlClient.SqlConnection(connectionString, new MemoryCache()))
using (var db = new DataClasses1DataContext(connection))
{
var array = db.Products.Where(__ => __.Price > 500).OrderBy(_ => _.Price).Take(200).ToArray();
}
のように、データベース接続を別クラスに差し替えるだけ。
実際には少しばかりの準備(=プロジェクトに上記 SqlConnection の代替えクラスおよび SqlCommand の代替えクラス、キャッシュ実装クラスを追加)は必要だけど、クエリの記述や結果の扱いに変更を加えないで済むのがミソ。
これでどれくらいのパフォーマンス向上があるかとゆーと…
| Id | int |
| Name | nvarchar(100) |
| Price | int |
とゆー構成で1000項目が登録されたテーブルに対して
private static IEnumerableExecuteQuery(DataClasses1DataContext db, int queries) { var list = new List (); for (int i = 0; i < queries; i++) { list.Add(db.Products.Where(__ => __.Price > 500).OrderBy(_ => _.Price).Take(200).ToArray()); } return list; }
とゆータスク(queries = 10 に設定)を100件連続して実行した場合の実行時間は以下のとおり。
| 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | |
| 通常の LINQ to SQL | 5.453s | 3.693s | 2.811s | 2.84s | 2.827s |
| LINQ to SQL + サンプルキャッシュ実装 | 0.942s | 0.909s | 0.906s | 0.909s | 0.906s |
てなカンジ。
概ね3倍のパフォーマンス向上が見られるのでありました。
↑と同じ設定のタスクを ThreadPool を使って同時並行に実行すると
| 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | |
| 通常の LINQ to SQL | 0.964s | 0.972s | 0.976s | 1.004s | 0.987s |
| LINQ to SQL + サンプルキャッシュ実装 | 0.277s | 0.271s | 0.27s | 0.288s | 0.269s |
とゆー結果。あれ?思ったほど伸びてない (^^;
実行時間そのものはマルチスレッド化によって短縮されているものの、「約3倍のパフォーマンス向上」てとこには変化ナシ…
サンプルではローカルのファイルアタッチ DB を相手にしているので、そのせいかな?
ネットワーク接続の DB なら、キャッシュヒットした時の利益はひとしおなので、もうちょっと差が出るハズ。
ところで、実は「LINQ to SQL のパフォーマンスを向上する」のであれば
- CompiledQuery を利用する
- 結果セットをキャッシュする
のが定番コース。
なのに、なんでわざわざこんな風変わりなアプローチをしているのかとゆーと…
↑で挙げた代表的な2つの手法には
- クエリ実行のコードの書き換えが必要(=透過的でない)
- SqlDependency によるキャッシュエントリの効率的なエクスパイアを実行しづらい
という短所があるから。
前者は(必要なら)我慢するとしても、後者はもうちょっとどーにかしたいところ。
で、今回採用した「LINQ to SQL の DataContext に、DbConnection.CreateCommand() で DbCommand.ExecuteReader メソッドの処理を外部に移譲する DbCommand 派生クラスインスタンスを返す DbConnection 派生クラスインスタンスを渡す(長っ)」という手法なら
- DataContext を new する時のコードをホンの少し書き換えるだけ
- 結果セットのキャッシュを SqlCommand 相当のクラス内で行うので、SqlDependency を容易に扱える
となるワケ。(SqlDependency を使ったキャッシュ実装はまだ作ってないけど (^^;)
あと、LINQ to SQL のクエリ結果と違って、この層なら結果セットに含まれる値の型はごくシンプルなモノばかりなので
LINQ to SQL のクエリ結果を分散ストレージにキャッシュする
なんてことも理論的には可能だったりする。
てか、実のところ、今回の投稿はそれを検討するため下地だったりなんかして (^^;
なお、サンプルプロジェクトは VS2010 で作ったけど、ターゲットは .NET3.5SP1 なので、ソースファイルを抜き出してプロジェクトを再構成すれば VS2008 でも動作するはず。
PowerShell でジェネリックメソッドを呼び出す
PowerShell の長所の一つとして「.NET Framework との親和性が高い」ことが挙げられるわけですが、いかんせんジェネリックメソッドの呼び出しはちょとシンドイ。
というわけで、ジェネリックメソッド呼び出しのメンドクサさを隠蔽すべく
Param(
[Parameter(Mandatory=$True, HelpMessage="ジェネリックメソッドを公開する型を指定します。")]
[ValidateNotNull()]
[Type] $Type,
[Parameter(Mandatory=$True, HelpMessage="ジェネリックメソッドの名前を指定します。")]
[ValidateNotNullOrEmpty()]
[String] $MethodName,
[Parameter(Mandatory=$True, HelpMessage="ジェネリック型パラメータに与える Type 型の配列を指定します。")]
[ValidateNotNullOrEmpty()]
[Type[]] $GenericArgumentTypes,
[Parameter(HelpMessage="ジェネリックメソッド呼び出しの対象となるオブジェクトインスタンスを指定します。")]
[Object] $Object = $Null,
[Parameter(HelpMessage="ジェネリックメソッド呼び出しの引数となる Object 型の配列を指定します。")]
[ValidateNotNull()]
[Object[]] $ArgumentList = @())
Trap { break }
$MethodInfo = $Type.GetMethods() |
Where-Object {$_.Name -eq $MethodName} |
Where-Object {$_.IsGenericMethod} |
Where-Object {$MethodInfo.GetGenericArguments().Length -eq $GenericArgumentTypes.Length} |
Select-Object -First 1
$GenericMethodInfo = $MethodInfo.MakeGenericMethod($GenericArgumentTypes)
$GenericMethodInfo.Invoke($Object, $ArgumentList)
なんてスクリプトを書いてみた。
使い方は
Invoke-GenericMethod.ps1 -Type Linq.Enumerable -MethodName Repeat -GenericArgumentTypes Int32 -ArgumentList @(9,4)
みたいな感じ。
これは、C# の
Enumerable.Repeat<int>(9, 4);
に相当。
ちょっと引数が多いけど、汎用的に仕上げたかったのでこんなモンでしょう。
必要に応じて
Function EnumerableRepeat([Int32] $Element, [Int32] $Count)
{
Invoke-GenericMethod.ps1 -Type Linq.Enumerable -MethodName Repeat -GenericArgumentTypes Int32 -ArgumentList @(9,4)
}
みたいなラッパ関数を書くってのも手。
文字列の中から数字だけ取り出すには?
ネタ元: 文字列の中から数字だけ取り出すには?
文字列に含まれた複数の数字列を取り出して連結。
もっと簡潔に…書けるかなぁ?
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
string text = "あ1いう3え4お56789";
Console.WriteLine(
string.Concat(Regex.Matches(text, @"\d+").Cast().Select(_ => _.Value).ToArray()));
}
}
}
追記: 2010-06-04
Twitter でいくつかフィードバックを貰ったので、再考。
正規表現を使ってないので、この系統の方が性能的には有利なはず。
でも、char[] から string を得るのに string(char []) を使っているところが、ワンライナー的には許せないw
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
string text = "あ1いう3え4お56789";
Console.WriteLine(
new string(text.Where(_ => char.IsDigit(_)).ToArray()));
}
}
}
さらに追記: 2010-06-04
お題を消化するだけならコレが最強w
^ による否定をすっかり忘れてたヨ…
あーんど、なんでもかんでも LINQ ろうと思ってはいけないね (^^;
using System;
using System.Text.RegularExpressions;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
string text = "あ1いう3え4お56789";
Console.WriteLine(
Regex.Replace(text, @"[^\d]", string.Empty));
}
}
}
0 から 1000 までに含まれる ‘0’ を数える
ネタ元: gist: 415551 – from http://d.hatena.ne.jp/os0x/20081115/1226770265- GitHub
Sum() しない版。
足し算で「個数を求める」のではなく、「数える」という点を強調したつもり。
あと、ネストをするとラムダ式の引数の名前考えるのがめんどくさい (^^; ので、SelectMany() を使って、早い段階でフラットな IEnumerable に変換してしまっているのもミソ。
using System;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(
Enumerable.Range(0, 1001).SelectMany(_ => _.ToString()).Count(_ => _ == '0'));
}
}
}
勝手に競演
ネタ元: 夢の競演みたび
そのままだと面白くないので、複数除外できるようにしてみた。
簡素さを優先するとこんな感じ。excludes を何度も舐めるのが美しくないけど。
result を何度も列挙するような場合、result.ToArray() すればいいよね。
1回しか回さないなら、これでおk。
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var source = new[]
{
new { Name = "しゅうたん", Type = "アメショ" },
new { Name = "ろり", Type = "アメショ" },
new { Name = "みずきちゃん", Type = "スコティ" },
new { Name = "マグさん", Type = "ほげ" },
new { Name = "?", Type = "もげ" },
};
var excludes = new[] { "ほげ", "もげ" };
var result = source.Where(_ => !excludes.Contains(_.Type));
foreach (var item in result)
{
System.Console.Out.WriteLine("Name = {0}, Type = {1}", item.Name, item.Type);
}
}
}
}
Windows Server 2008 R2 で? TFS Basic 2010 日本語版 Beta2 の TFS Web Access が利用できない
元ネタ: TFS Basicを構成するとWeb Accessが使用できない – 新日々此何有哉
元ネタの元ネタは僕なんですが、最近 Twitter ばっかりで全然ブログ書いてないから、でたまにはってことで (^^;
現象としては件名の通りです。
Windows Server 2008 R2 をクリーンインストールした後(さらに、必要ならドメインに登録して)、TFS Basic 2010 Beta2 をインストーラーの言いなりでインストールすると、TFS Web Access が利用できません。
原因は詳しく調べていませんが、雰囲気的には TFS のインストーラが ASP.NET AJAX 系の構成をしくじってる風味?
kkamegawa さんが現象を再現してくれるのと前後して、Microsoft Connect に以下の障害報告してました
Windows Server 2008 R2 日本語版に TFS Basic 日本語版をすべて既定の設定でインストールした時、TFS Web Access が使用できないhttps://connect.microsoft.com/VisualStudioJapan/feedback/ViewFeedback.aspx?FeedbackID=510612
が、昨夜「再現した。これは日本語版に固有の問題ではない。次のリリースで修正する」という回答がついてました。
ウチの環境固有の問題じゃなくてよかった (^^;
なお、Beta2 時点での回避策は「手動で .NET Framework 3.5.1 をイネーブルにしてくれ」だそうです。
Windows Server 2008 R2 の管理コンソールから出来たはずだけど、既に .NET Framework 4 が入ってる(TFS のインストーラが入れてった)状態で大丈夫なんだろうか? (^^;
まだお試し構築中だから、最悪 OS の入れなおし(つっても保存してある .vhd をコピってくるだけ)からやってもいいんだけど。