42

非常に短い質問です。ランダムにソートされた大きな文字列配列 (100K+ エントリ) があり、目的の文字列の最初の出現を見つけたいと考えています。私には2つの解決策があります。

私が推測できることを読んだことから、「forループ」は現在わずかに優れたパフォーマンスを提供することになります(ただし、このマージンは常に変化する可能性があります)が、linqバージョンの方がはるかに読みやすいこともわかります。結局、どの方法が現在のベスト コーディング プラクティスと一般的に考えられていますか? また、その理由は何ですか?

string matchString = "dsf897sdf78";
int matchIndex = -1;
for(int i=0; i<array.length; i++)
{
    if(array[i]==matchString)
    {
        matchIndex = i;
        break;
    }
}

また

int matchIndex = array.Select((r, i) => new { value = r, index = i })
                         .Where(t => t.value == matchString)
                         .Select(s => s.index).First();
4

9 に答える 9

59

ベスト プラクティスは、必要なものによって異なります。

  1. 開発速度と保守性: LINQ
  2. パフォーマンス (プロファイリング ツールによる): 手動コード

LINQ は、すべての間接化によって実際に処理速度を低下させます。コードの 99% はエンド ユーザーのパフォーマンスに影響を与えないため、心配する必要はありません。

私は C++ から始めて、コードを最適化する方法を実際に学びました。LINQ は、CPU を最大限に活用するのには適していません。したがって、LINQ クエリが問題であると判断した場合は、それを破棄してください。しかし、その時だけ。

あなたのコード サンプルでは、​​3 倍の速度低下を見積もっています。割り当て (およびその後の GC!) と、ラムダによる間接参照は、非常に厄介です。

于 2013-02-15T11:42:23.037 に答える
34

少しは性能アップ?ループにより、パフォーマンスが大幅に向上します。

以下のコードを検討してください。RELEASE(デバッグではない)ビルドの私のシステムでは、次のようになります。

Found via loop at index 999999 in 00:00:00.2782047
Found via linq at index 999999 in 00:00:02.5864703
Loop was 9.29700432810805 times faster than linq.

コードは、見つけられる項目が最後に来るように意図的に設定されています。それが最初から正しかったとしたら、状況はまったく違ったものになっていたでしょう。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Demo
{
    public static class Program
    {
        private static void Main(string[] args)
        {
            string[] a = new string[1000000];

            for (int i = 0; i < a.Length; ++i)
            {
                a[i] = "Won't be found";
            }

            string matchString = "Will be found";

            a[a.Length - 1] = "Will be found";

            const int COUNT = 100;

            var sw = Stopwatch.StartNew();
            int matchIndex = -1;

            for (int outer = 0; outer < COUNT; ++outer)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    if (a[i] == matchString)
                    {
                        matchIndex = i;
                        break;
                    }
                }
            }

            sw.Stop();
            Console.WriteLine("Found via loop at index " + matchIndex + " in " + sw.Elapsed);
            double loopTime = sw.Elapsed.TotalSeconds;

            sw.Restart();

            for (int outer = 0; outer < COUNT; ++outer)
            {
                matchIndex = a.Select((r, i) => new { value = r, index = i })
                             .Where(t => t.value == matchString)
                             .Select(s => s.index).First();
            }

            sw.Stop();
            Console.WriteLine("Found via linq at index " + matchIndex + " in " + sw.Elapsed);
            double linqTime = sw.Elapsed.TotalSeconds;

            Console.WriteLine("Loop was {0} times faster than linq.", linqTime/loopTime);
        }
    }
}
于 2013-02-15T12:17:32.003 に答える
6

宣言型パラダイムによると、LINQ は制御フローを記述せずに計算のロジックを表現します。クエリは目標指向で自己記述的であるため、分析と理解が容易です。も簡潔です。さらに、LINQ を使用すると、データ構造の抽象化に大きく依存します。これには、高い保守性と再利用性が含まれます。

反復アプローチは、命令型パラダイムに対処します。きめ細かな制御が可能になるため、より高いパフォーマンスを容易に得ることができます。コードのデバッグも簡単です。適切に構成された反復は、クエリよりも読みやすい場合があります。

于 2013-02-15T13:22:08.230 に答える
3

パフォーマンスと保守性の間には常にジレンマがあります。そして通常(パフォーマンスに関する特定の要件がない場合)、保守性が優先されます。パフォーマンスに問題がある場合にのみ、アプリケーションのプロファイルを作成し、問題の原因を見つけて、パフォーマンスを向上させる必要があります(同時に保守性を低下させることにより、そうです、それが私たちの住む世界です)。

サンプルについて。Linqは、コードに一致の保守性を追加しないため、ここではあまり良いソリューションではありません。実際、私にとって、投影、フィルタリング、および投影は、単純なループよりもさらに悪く見えます。ここで必要なのは、ループよりも保守が容易で、ほぼ同じパフォーマンスを持つ単純なArray.IndexOfです。

Array.IndexOf(array, matchString)
于 2013-02-15T12:05:50.330 に答える
2

さて、あなたはあなた自身の質問に答えました。

For最高のパフォーマンスが必要な場合はループを使用し、可読性が必要な場合はループを使用しLinqます。

また、インラインラムダ式の恩恵を受ける Parallel.Foreach() を使用する可能性を念頭に置いてください (したがって、Linq により近い)。これは、「手動で」並列化を行うよりもはるかに読みやすいです。

于 2013-02-15T11:43:31.187 に答える
1

どちらも、LINQを見るのを好む人もいれば、そうでない人もいるベストプラクティスとは見なされていないと思います。

パフォーマンスが問題である場合は、シナリオの両方のコードをプロファイリングします。違いが無視できる場合は、より適合していると感じるものを使用します。結局のところ、コードを管理しているのはあなたです。

また、PLINQを使用したり、ループを並行して実行したりすることを考えましたか?

于 2013-02-15T11:45:00.743 に答える
0

最良のオプションは、Array クラスの IndexOf メソッドを使用することです。配列に特化しているため、Linq と For ループの両方よりも大幅に高速になります。Matt Watsons Answer の改善。

using System;
using System.Diagnostics;
using System.Linq;


namespace PerformanceConsoleApp
{
    public class LinqVsFor
    {

        private static void Main(string[] args)
        {
            string[] a = new string[1000000];

            for (int i = 0; i < a.Length; ++i)
            {
                a[i] = "Won't be found";
            }

            string matchString = "Will be found";

            a[a.Length - 1] = "Will be found";

            const int COUNT = 100;

            var sw = Stopwatch.StartNew();

            Loop(a, matchString, COUNT, sw);

            First(a, matchString, COUNT, sw);


            Where(a, matchString, COUNT, sw);

            IndexOf(a, sw, matchString, COUNT);

            Console.ReadLine();
        }

        private static void Loop(string[] a, string matchString, int COUNT, Stopwatch sw)
        {
            int matchIndex = -1;
            for (int outer = 0; outer < COUNT; ++outer)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    if (a[i] == matchString)
                    {
                        matchIndex = i;
                        break;
                    }
                }
            }

            sw.Stop();
            Console.WriteLine("Found via loop at index " + matchIndex + " in " + sw.Elapsed);

        }

        private static void IndexOf(string[] a, Stopwatch sw, string matchString, int COUNT)
        {
            int matchIndex = -1;
            sw.Restart();
            for (int outer = 0; outer < COUNT; ++outer)
            {
                matchIndex = Array.IndexOf(a, matchString);
            }
            sw.Stop();
            Console.WriteLine("Found via IndexOf at index " + matchIndex + " in " + sw.Elapsed);

        }

        private static void First(string[] a, string matchString, int COUNT, Stopwatch sw)
        {
            sw.Restart();
            string str = "";
            for (int outer = 0; outer < COUNT; ++outer)
            {
                str = a.First(t => t == matchString);

            }
            sw.Stop();
            Console.WriteLine("Found via linq First at index " + Array.IndexOf(a, str) + " in " + sw.Elapsed);

        }

        private static void Where(string[] a, string matchString, int COUNT, Stopwatch sw)
        {
            sw.Restart();
            string str = "";
            for (int outer = 0; outer < COUNT; ++outer)
            {
                str = a.Where(t => t == matchString).First();

            }
            sw.Stop();
            Console.WriteLine("Found via linq Where at index " + Array.IndexOf(a, str) + " in " + sw.Elapsed);

        }

    }

}

出力:

Found via loop at index 999999 in 00:00:01.1528531
Found via linq First at index 999999 in 00:00:02.0876573
Found via linq Where at index 999999 in 00:00:01.3313111
Found via IndexOf at index 999999 in 00:00:00.7244812
于 2018-11-28T12:34:27.303 に答える
0

少し答えがなく、実際にはhttps://stackoverflow.com/a/14894589の単なる拡張ですが、私はオンとオフを問わず、Linq-to-Objects の API 互換の代替品に取り組んできました。今のうちに。手動でコード化されたループのパフォーマンスはまだ提供されていませんが、多くの (ほとんどの?) linq シナリオでは高速です。それはより多くのガベージを作成し、わずかに重い初期費用がかかります.

コードはhttps://github.com/manofstick/Cistern.Linqで入手できます

nuget パッケージが利用可能ですhttps://www.nuget.org/packages/Cistern.Linq/ (これが強化されているとは言えません。自己責任で使用してください)

マシュー・ワトソンの回答 ( https://stackoverflow.com/a/14894589 ) からコードを取得し、2 つのわずかな調整を加えると、手作業でコード化されたループよりも約 3.5 倍悪い時間になります。私のマシンでは、元の System.Linq バージョンの約 1/3 の時間がかかります。

置き換える 2 つの変更:

using System.Linq;

...

matchIndex = a.Select((r, i) => new { value = r, index = i })
             .Where(t => t.value == matchString)
             .Select(s => s.index).First();

以下を使用します。

// a complete replacement for System.Linq
using Cistern.Linq;

...

// use a value tuple rather than anonymous type
matchIndex = a.Select((r, i) => (value: r, index: i))
             .Where(t => t.value == matchString)
             .Select(s => s.index).First();

そのため、ライブラリ自体は進行中の作業です。corefx の System.Linq テスト スイートのいくつかのエッジ ケースに失敗します。また、いくつかの関数を変換する必要があります (現在、パフォーマンスの観点ではないにしても、API の観点から互換性のある corefx System.Linq 実装があります)。しかし、助けたい人、コメントなどをいただければ幸いです....

于 2019-08-19T04:35:27.813 に答える