LINQ とは
LINQ(Language Integrated Query)とは、配列・リスト・DB・XML・JSON・ファイルなど、あらゆるデータに対して、同じ書き方で検索・抽出・変換をすることのできる仕組みです。
主に、SQLのような書き方をし、C#のコードの中でデータを操作します。
いつから使えるの?
.NET Framework 3.5 (2007年頃)から導入されました。C#の言語バージョンは 3.0 からとなります。現在も継続して使える機能の一つで、チューニングされ続けています。
- .NET Framework 3.5(2007年)
- C# 3.0
このタイミングで、 - ラムダ式
- 拡張メソッド
- 匿名型
- LINQ
がまとめて導入されました。
現在の .NET(Core / 5〜8)でも当然使えます。
LINQの最適化と「LINQ to Objects」と「LINQ to SQL」の違い
LINQ の最適化は “LINQ to Objects” と “LINQ to SQL” で挙動が違うってこと。
Linux では特に LINQ to Objects の最適化が弱く、Windows より遅くなるケースがある。
つまり:
- SQL(Entity Framework / LINQ to SQL)
→ DB 側が最適化するので OS による差はほぼ出ない - メモリ上のデータ(LINQ to Objects)
→ 実行環境(Windows / Linux)で最適化の差が出る
→ 特に大量データでは顕著
要件としては次の通り
Linux では
- 配列数が 10万件を超える場合は、LINQ to Objects を使って where 等はしない方が良い...
Windows では
- 配列数が多くても LINQ to Objects を使う方が早い...
これは、LinuxとWindows のLINQ に対する最適化やインライン化、OSそのものの仕組みの影響なのか、30万件のデータ処理をしてみると、Windows では LINQ がはやく、Linux では野良書きの方が処理速度が速いという結果に
実際に検証した実行環境
検証した内容
linux 環境(本サーバー)
- Rocky Linux 9.7
- Dotnet core 8.0(ASP.NET Core-SDK)
- CPU: AMD Ryzen Threadripper(32core/64thread)
- DDR4 128GB
- M2(PCIe4)
windows(ASP.NET開発環境)
- Microsoft Windows 11 Professional
- AMD Ryzen 7 5850U
- DDR4 32GB
- M2(PCIe3)
DB に商品情報を 40万件登録している。その構造を元に、staticメモリ上に設置
例:
class Products {
public ulong Id {set;get;}
public Guid Guid {set;get;}
public string Category {set;get;}
public string ProductTitle {set;get;}
...
}
上記構造を
public static Dictionary<ulong, Products> ProductsCache = new();
init 処理にて...
ProductsCach = await _context.Products.AsNoTracking().Where( g => g.Stauts == 1 ).ToDictionaryAsync( g => (ulong)g.Id, g => g );
とし、DBから読み込んだ40万件の結果を ProductsCache に放り込む。なぜ、static にして入れているかは、mariadb を利用しており、日本語検索が弱く、Mroonga では、候補がなかなか引っかからず、LIKE をすると、1サーバーで搭載してのサービス提供なので、DBでは抽出が遅すぎて想定の時間に裁けなかった。しかたないので、物理メモリに押し込んで無理矢理やることにした。
そして、なんか、Windowsでやったときより遅いなーと思って、試しに調べてみると、確かに、Windowsは早いけど、Linux遅い...っていうことに気が付いた。
ちなみに、どうやって調べたのかというと
[HttpGet]
[Route("/")]
public ResponseResult GetListProducts(int page, int line = 100, string sort = "", string orderby = "asc") {
var start = DateTime.Now;
... 処理
return new {
RuntimeAt = (DateTime.Now - start).TotalMilliseconds,
....
}
}
... 処理部に、LINQ to Object による条件式を使って取得する。このとき、処理部に 取得条件やorderby などがはいってくる。この処理を linq to objects を使うと遅くなるが、いくつか条件があり、where 区や any 区が遅いようで、Orderby と .OrderByDescending 区については、あまり遅くない気がしている。(現在、いろいろと検証中)
ちなみに、何があったの
Linuxにて、static のメモリ上に抽出したデータ(40万件)の検索をかけて抽出するコードを、linqを使って実装していた(DBでの検索だと遅すぎるので)。抽出結果は100件毎に json としてまとめて返すようにし、page 処理で分けるようにしていた(つまり、WebApi)。 結果は、windows では600ms で結果を返し、 linux では 3000ms 秒もかかることが判明した。
原因は何かと探った結果、linq の最適化がWindows/Linuxでは違うようで、foreach や List に変換した昔ならあのコーティングをすると、windows と同等の抽出結果になった。 このことから、遅延実行をしたい場合やEntityFramework としてのSQLとして利用する場合を除く、メモリ上のデータをやり取りする場合は、LINQを使わない方が良いという考えになった。
ところが、野良書き(LINQ to Objects を使わず、foreach 等を使って書いたコード)は、Windowsでは、linqよりも遅くなってしまった。この結果、最適化が強い部分が、OSの影響で変わっているようなので、コーティングする場合は、LINQを積極的に使うのがいいか、昔ながらのコーティングのがいいのか考える必要があることがわかった。