web-dev-qa-db-ja.com

LINQは遅いので避けるべきですか?

.net linqは非常に遅いので使用しないでください。他の誰かが同じ結論に達したのではないかと思っていました。例は次のとおりです。

1000000を実行するために1443msを要した場合、非LINQを比較します。
LINQと比較して1000000000を実行するには、4944ミリ秒かかりました。
(243%遅い)

非LINQコード:

for (int i = 0; i < 10000; i++)
{
    foreach (MyLinqTestClass1 item in lst1) //100000 items in the list
    {
        if (item.Name == "9999")
        {
            isInGroup = true;
            break;
        }
    }
}

1000000を実行するために1443msを要した場合、非LINQを比較します。

LINQコード:

for (int i = 0; i < 10000; i++)  
    isInGroup = lst1.Cast<MyLinqTestClass1>().Any(item => item.Name == "9999");  

1000000000を実行するために4944msを要したのは、LINQと比較した場合です。

LINQコードを最適化することは可能だと思いますが、非常に遅いLINQコードを簡単に取得でき、使用しないことを考えると、 LINQが遅いとすると、PLINQも遅く、NHibernate LINQも遅いので、どのような種類のLINQステートメントも使用しないでください。

他の誰かが、LINQが遅いのでLINQを使用したくないと思ったことはありますか?または、このようなベンチマークに基づいてあまりにも一般的な結論を下していますか?

57
user455095

Linqは遅いので避けるべきですか?

いいえ。十分に速くない場合は避けてください。 遅い十分に速くないはまったく同じではありません!

遅いは、顧客、経営陣、利害関係者には関係ありません。 十分に速くないは非常に関連があります。 速い何かがどれだけであるか測定しないでください。これは、ビジネス上の決定の基礎として使用できることは何も伝えていません。 顧客に受け入れられる程度を測定します。それが許容できる場合は、それを速くするためにお金を使うのをやめてください。それはすでに十分です。

パフォーマンスの最適化は(= /// =)高価です。他の人が読み取って保守できるようにコードを書くのは高価です。これらの目標は互いに相反することが多いため、利害関係者のお金を責任を持って使うためには、ではないことにパフォーマンスの最適化を行うだけで貴重な時間と労力を費やしていることを確認する必要があります。十分に速い

LINQコードが他の方法でコードを書くよりも遅い人工的で非現実的なベンチマーク状況を見つけました。あなたの顧客があなたの非現実的なベンチマークの速度を少しも気にしないことを保証します。彼らはあなたが彼らに出荷しているプログラムが彼らにとって遅すぎる場合にのみ気にかけます。そして私はあなたを保証します、あなたの経営陣はそれについて少し気にしない(彼らが有能であるなら)。彼らは、十分に速いものすごく速いものを作るために不必要に費やしているお金の量に気を配り、コードを読んで理解し、その過程で維持するためにコードをより高価にする。

233
Eric Lippert

なぜCast<T>()を使用しているのですか?基本的に、実際にベンチマークを判断するのに十分なコードを提供していません。

はい、あなたはcan LINQを使用して遅いコードを記述します。何だと思う?遅い非LINQコードも記述できます。

LINQ 大いには、データを処理するコードの表現力を支援します...そして、LINQを理解するために時間をかけさえすれば、パフォーマンスの高いコードを書くことはそれほど難しくありません。

誰かがme LINQ(特にLINQ to Objects)をperceivedの理由で使用しないように言った場合、私は彼らの顔で笑うでしょう。彼らが特定のボトルネックを思いつき、「この状況でLINQを使用しないことでこれをより速くすることができます。ここに証拠があります」と言った場合、それは非常に異なる問題です。

152
Jon Skeet

多分私は何かを逃したかもしれませんが、私はあなたのベンチマークがオフであることをかなり確信しています.

私は次の方法でテストしました:

  • Any拡張メソッド( "LINQ")
  • 単純なforeachループ(「最適化された」メソッド)
  • ICollection.Containsメソッドの使用
  • 最適化されたデータ構造を使用するAny拡張メソッド(HashSet<T>

ここにテストコードがあります:

class Program
{
    static void Main(string[] args)
    {
        var names = Enumerable.Range(1, 10000).Select(i => i.ToString()).ToList();
        var namesHash = new HashSet<string>(names);
        string testName = "9999";
        for (int i = 0; i < 10; i++)
        {
            Profiler.ReportRunningTimes(new Dictionary<string, Action>() 
            {
                { "Enumerable.Any", () => ExecuteContains(names, testName, ContainsAny) },
                { "ICollection.Contains", () => ExecuteContains(names, testName, ContainsCollection) },
                { "Foreach Loop", () => ExecuteContains(names, testName, ContainsLoop) },
                { "HashSet", () => ExecuteContains(namesHash, testName, ContainsCollection) }
            },
            (s, ts) => Console.WriteLine("{0, 20}: {1}", s, ts), 10000);
            Console.WriteLine();
        }
        Console.ReadLine();
    }

    static bool ContainsAny(ICollection<string> names, string name)
    {
        return names.Any(s => s == name);
    }

    static bool ContainsCollection(ICollection<string> names, string name)
    {
        return names.Contains(name);
    }

    static bool ContainsLoop(ICollection<string> names, string name)
    {
        foreach (var currentName in names)
        {
            if (currentName == name)
                return true;
        }
        return false;
    }

    static void ExecuteContains(ICollection<string> names, string name,
        Func<ICollection<string>, string, bool> containsFunc)
    {
        if (containsFunc(names, name))
            Trace.WriteLine("Found element in list.");
    }
}

Profilerクラスの内部について心配する必要はありません。ループでActionを実行し、Stopwatchを使用して時間を計測します。また、各テストの前に必ずGC.Collect()を呼び出して、可能な限り多くのノイズを除去します。

結果は次のとおりです。

      Enumerable.Any: 00:00:03.4228475
ICollection.Contains: 00:00:01.5884240
        Foreach Loop: 00:00:03.0360391
             HashSet: 00:00:00.0016518

      Enumerable.Any: 00:00:03.4037930
ICollection.Contains: 00:00:01.5918984
        Foreach Loop: 00:00:03.0306881
             HashSet: 00:00:00.0010133

      Enumerable.Any: 00:00:03.4148203
ICollection.Contains: 00:00:01.5855388
        Foreach Loop: 00:00:03.0279685
             HashSet: 00:00:00.0010481

      Enumerable.Any: 00:00:03.4101247
ICollection.Contains: 00:00:01.5842384
        Foreach Loop: 00:00:03.0234608
             HashSet: 00:00:00.0010258

      Enumerable.Any: 00:00:03.4018359
ICollection.Contains: 00:00:01.5902487
        Foreach Loop: 00:00:03.0312421
             HashSet: 00:00:00.0010222

データは非常に一貫しており、次のストーリーを伝えています。

  • Any拡張メソッドを単純に使用すると、foreachループを使用した場合よりも約9%遅くなります。

  • 最適化されていないデータ構造(ICollection<string>.Contains)で最も適切なメソッド(List<string>)を使用すると、単純にforeachループを使用するよりも約50%速くなります

  • 最適化されたデータ構造(HashSet<string>)を使用すると、パフォーマンスの面で他のすべてのメソッドが完全に無効になります。

あなたが243%をどこから得たのか私にはわかりません。私の推測では、それはそのすべてのキャストと関係があります。 ArrayListを使用している場合は、最適化されていないデータ構造を使用しているだけでなく、主に廃止されたデータ構造を使用しています。

次に何が起こるか予測できます。 「ええ、私はそれをより良く最適化できることを知っていますが、これはLINQと非LINQのパフォーマンスを比較するための単なる例でした。」

ええ、でも、もしあなたの例で徹底することができなかったなら、どうやってプロダクションコードでこれを徹底することを期待できますか?

一番下の行はこれです:

ソフトウェアをどのように設計および設計するかは、どの特定のツールをいつ使用するかよりも指数関数的に重要です。

LINQを使用する場合と使用しない場合で発生する可能性が高いパフォーマンスのボトルネックに遭遇した場合は、問題を解決してください。 Ericの自動パフォーマンステストの提案は優れたものです。問題を早期に特定して問題を適切に解決できるようにする-生産性を80%向上させるが、たまたま10%未満になる驚くべきツールを回避しないパフォーマンスが低下しますが、実際には問題を調査し実際の解決策によって2倍にパフォーマンスを向上させることができます。 、または10、または100以上。

高性能アプリケーションを作成することは、適切なライブラリを使用することではありません。 プロファイリング、優れたデザインの選択、優れたコードの記述についてです。

84
Aaronaught

LINQは実際のボトルネックですか(アプリケーションの全体的なパフォーマンスまたは知覚されるパフォーマンスに影響します)?

アプリケーションは、実際の1,000,000,000以上のレコードに対してこの操作を実行しますか?そうだとすれば、代替案を検討したいと思うかもしれませんが、そうでない場合は、「このファミリーセダンは180+ MPHでうまく運転できないため、購入できません」というようなものです。

それが「ただ遅い」場合は、それはあまり良い理由ではありません...その理由から、すべてをasm/C/C++で記述し、C#は「遅すぎる」ことを考慮してテーブルからはずす必要があります。

16
STW

時期尚早な悲観化は時期尚早の最適化と同じくらい悪いですが、使用状況を考慮せずに絶対速度に基づいてテクノロジー全体を除外すべきではありません。はい、あなたがいくつかの非常に重い数の処理をしている場合そしてこれはボトルネックです、LINQ問題がある可能性があります-プロファイルそれ。

LINQを支持して使用できる引数は、おそらく手書きのコードでそれをしのぐことができますが、LINQバージョンはより明確で維持しやすい可能性があります-さらに、複雑な手動並列化と比較してPLINQの利点があります。

12
snemarch

この種の比較の問題は、抽象的には意味がないことです。

NameプロパティでMyLinqTestClass1オブジェクトをハッシュすることから始めれば、これらのどちらかを打ち負かすことができます。それらの間で、名前でそれらをソートし、後で二分検索を行うことができるかどうか。実際、そのためにMyLinqTestClass1オブジェクトを保存する必要はありません。名前を保存するだけです。

メモリサイズに問題がありますか?名前をDAWG構造に保存し、十分なものを組み合わせてから、このチェックに使用しますか?

これらのデータ構造を設定する際の余分なオーバーヘッドは意味がありますか?言うことは不可能です。

もう1つの問題は、その名前であるLINQの概念に関する別の問題です。マーケティングの目的でMSが「ここで一緒に機能するクールな新しいものはたくさんある」と言えることは素晴らしいことですが、それらをバラバラにする必要がある種類の分析を行っているときに、それらを一緒に組み合わせる人々に関してはあまり良くありません。 。 Anyへの呼び出しを行う必要があります。これは、.NET2.0日で一般的なフィルター付き列挙可能パターンを実装します(.NET1.1では不明ではありませんが、書くのが面倒なので、特定のケースでの効率の利点が本当に重要な場合に使用されます)、ラムダ式があり、クエリツリーがすべて1つの概念にまとめられています。遅いのはどれですか?

私はここでの答えはラムダであり、Anyの使用ではないに違いないと思いますが、私は大量(プロジェクトの成功など)には賭けません。テストして確認します。一方、ラムダ式がIQueryableで動作する方法は、ラムダを使用せずに同等の効率で記述することが非常に困難な、特に効率的なコードを作成することができます。

LINQが人為的なベンチマークに失敗したため、LINQの効率が良い場合、効率が良くなりませんか?私はそうは思いません。

理にかなったところでLINQを使用してください。

ボトルネック状態では、最適化として適切または不適切であるように見えても、その後が離れるか、LINQに移動します。実際の最適化を難しくするだけなので、最初は理解しにくいコードを記述しないでください。

7
Jon Hanna

私には、これは契約に取り組んでいるようであり、雇用主はLINQを理解していないか、システムのパフォーマンスのボトルネックを理解していないようです。 GUIを使用してアプリケーションを作成している場合、LINQを使用することによるパフォーマンスへの小さな影響は無視できます。典型的なGUI/Webアプリでは、メモリ内の呼び出しはすべての待機時間の1%未満です。あなた、またはあなたの雇用者は、その1%を最適化しようとしています。それは本当に有益ですか?

ただし、科学的または数学に重点を置いたアプリケーションを作成し、ディスクまたはデータベースへのアクセスが非常に少ない場合、LINQは適切ではないことに同意します。

ところで、キャストは必要ありません。以下は、最初のテストと機能的に同等です。

       for (int i = 0; i < 10000; i++)
            isInGroup = lst1.Any(item => item.Name == "9999");

10,000個のMyLinqTestClass1オブジェクトを含むテストリストを使用してこれを実行した場合、オリジナルは2.79秒で実行され、3.43秒で修正されました。 CPU時間の1%未満を占める可能性が高い操作で30%を節約することは、時間の有効利用ではありません。

4
Beep beep

たぶんlinqは遅いですが、linqを使用すると、コードを非常に簡単に並列化できます。

このような:

lst1.Cast<MyLinqTestClass1>().AsParallel().Any(item => item.Name == "9999");

どのようにサイクルを並列化しますか?

4
gandjustas

LINQが遅い結果としてnHibernateが遅いことに言及しているので、ここに興味深い観察があります。 LINQ to SQL(または同等のnHibernate)を実行している場合、LINQコードはSQLサーバー上のEXISTSクエリに変換されます。ループコードは最初にすべての行をフェッチしてから、それらを反復処理する必要があります。これで、このようなテストを簡単に作成して、ループコードがすべての10K実行に対してすべてのデータを1回読み取り(シングルDBルックアップ)、実際にLINQコードが10K SQLクエリを実行するようにすることができます。それはおそらく、実際には存在しないループバージョンの大きな速度上の利点を示すでしょう。実際には、単一のEXISTSクエリがテーブルスキャンよりも優れており、クエリされる列にインデックスがない場合でも、毎回ループします(このクエリが頻繁に実行される場合はそうなります)。

あなたのテストがそうであると言っているわけではありません-見るのに十分なコードがありません-しかし、それは可能性があります。また、LINQ to Objectsには実際にパフォーマンスの違いがあるかもしれませんが、LINQ to SQLにはまったく変換されない場合があります。何を測定しているのか、それが実際のニーズにどのように適用できるのかを知る必要があります。

3
tvanfosson

「.net linqは非常に遅いので(誰のために?)使用す​​べきではないと言われてきました。」

私の経験では、someoneonceが何を言っているかに基づいて、どの手法、ライブラリ、または言語を使用するかなどの決定は、悪いアイデア。

まず、情報は信頼できる情報源からのものですか?そうでない場合は、この人(おそらく不明)を信頼して設計上の決定を下すのを信じて、大きな間違いを犯している可能性があります。第二に、この情報は今日でも関連していますか?しかし、わかりました。シンプルであまり現実的ではないベンチマークに基づいて、LINQは手動で同じ操作を実行するよりも遅いと結論付けました。自問する自然な問題はこれです:このコードのパフォーマンスは重要ですか?このコードのパフォーマンスは、LINQクエリの実行速度よりもその他の要因によって制限されますか?データベースクエリ、I/Oの待機などを考えますか?

これが私が働きたい方法です:

  1. 解決する問題を特定し、すでに知っている要件と制限を考慮して、最も単純で機能的に完全なソリューションを記述します
  2. 実装が実際に要件を満たしているかどうかを判断します(十分な速さですか?リソース消費は許容レベルに保たれていますか?)。
  3. もしそうなら、あなたは終わりです。そうでない場合は、ソリューションが#2のテストに合格するまで、ソリューションを最適化および調整する方法を探します。ここが遅いかもしれませんが何かをあきらめることを検討する必要があります。多分。ただし、ボトルネックが、予想していた場所とはまったく異なる可能性があります。

私にとって、この単純な方法は単一の目的を果たします:maximizing生産性minimizingすでに完全に適切なコードの改善に費やす時間。

はい、あなたの元の解決策がそれをもはやカットしないことに気づく日が来るかもしれません。またはそうでないかもしれません。ある場合は、その場で対処してください。架空の(将来の)問題を解決するために時間を無駄にしないことをお勧めします。

3

はい、あなたが正しい。 LINQで遅いコードを書くのは簡単です。他のものも正しい:LINQなしでC#で遅いコードを書くのは簡単です。

私はCであなたと同じループを書いて、それは数ミリ秒速く実行されました。私がこれから引き出す結論は、C#自体が遅いということです。

LINQ-> loop展開と同様に、Cでは同じことを行うのに5行以上のコード行が必要になるため、書き込みが遅くなり、読み取りが難しくなり、バグが発生しやすくなり、見つけにくくなり、それらを修正しますが、10億回の反復ごとに数ミリ秒を節約することが重要である場合、それがしばしば必要になります。

1
Ken

私はむしろ、それが必須である場合を除いて、最も効率的なコードを書くためにあまりにも一生懸命に試みることを避けるべきだと言いたいです。

0
tia

LINQが遅いとすると、PLINQも遅く、NHibernate LINQも遅いので、どのような種類のLINQステートメントも使用しないでください。

これは方法が異なりますが、信じられないほど異なります。 10億回の操作全体で1.4秒と5秒は、データアクセス操作については関係ありません。

0
eglasius

実演したように、LINQコードよりもパフォーマンスの高い非LINQコードを作成することができます。しかし、その逆も可能です。 LINQが提供できるメンテナンス上の利点を考えると、LINQに起因する可能性のあるパフォーマンスのボトルネックに遭遇する可能性は低いので、LINQにデフォルト設定することを検討できます。

つまり、LINQが機能しないシナリオがいくつかあります。たとえば、大量のデータをインポートする場合、個々の挿入を実行する動作は、XMLのバッチでSQL Serverにデータを送信するよりも遅いことがあります。この例では、LINQ挿入が非LINQ挿入よりも高速であるということではなく、バルクデータインポートのために個々のSQL挿入を実行することはお勧めできません。

0
Mayo

あなたのテストケースは少し歪んでいます。 ANYオペレーターは、結果を列挙し始め、最初のインスタンスが見つかって終了した場合はtrueを返します。文字列の単純なリストでこれを試して、結果を確認してください。 LINQの回避に関する質問に答えるには、実際にLINQの使用に移行する必要があります。コンパイル時のチェックに加えて、複雑なクエリを実行するときにコードを読みやすくします。また、例ではキャスト演算子を使用する必要はありません。

string compareMe = "Success";
string notEqual = "Not Success";

List<string> headOfList = new List<string>();
List<string> midOfList = new List<string>();
List<string> endOfList = new List<string>();

//Create a list of 999,999 items
List<string> masterList = new List<string>();
masterList.AddRange(Enumerable.Repeat(notEqual, 999999));

//put the true case at the head of the list
headOfList.Add(compareMe);
headOfList.AddRange(masterList);

//insert the true case in the middle of the list
midOfList.AddRange(masterList);
midOfList.Insert(masterList.Count/2, compareMe);

//insert the true case at the tail of the list
endOfList.AddRange(masterList);
endOfList.Add(compareMe);


Stopwatch stopWatch = new Stopwatch();

stopWatch.Start();
headOfList.Any(p=>p == compareMe);
stopWatch.ElapsedMilliseconds.Dump();
stopWatch.Reset();

stopWatch.Start();
midOfList.Any(p=>p == compareMe);
stopWatch.ElapsedMilliseconds.Dump();
stopWatch.Reset();

stopWatch.Start();
endOfList.Any(p=>p == compareMe);
stopWatch.ElapsedMilliseconds.Dump();
stopWatch.Stop();
0
Steve G.

型キャストはもちろん、コードの速度を低下させます。そんなに気にするなら、少なくとも厳密に型指定されたIEnumerableを比較に使用しました。私は可能な限りLINQを使用しようとしています。コードがより簡潔になります。コードの命令的な詳細について心配する必要はありません。 LINQは機能的な概念です。つまり、発生したいことを詳しく説明し、その方法について心配する必要はありません。

0
A-Dubb