非常に短い質問。ランダムに並べ替えられた大きな文字列配列(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();
ベストプラクティスは、必要なものによって異なります。
LINQはすべてのインダイレクションで本当に速度を低下させます。コードの99%がエンドユーザーのパフォーマンスに影響を与えないため、心配する必要はありません。
私はC++から始め、コードを最適化する方法を実際に学びました。 LINQは、CPUを最大限に活用するには適していません。したがって、LINQクエリを測定して問題である場合は、それを捨ててください。しかし、その後のみ。
コードサンプルの場合、3倍の速度低下が予想されます。割り当て(および後続のGC!)とラムダを介した間接指定は、本当に痛いです。
わずかパフォーマンスの向上?ループを使用すると、パフォーマンスが大幅に向上します!
以下のコードを検討してください。 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);
}
}
}
LINQは、宣言的なパラダイムに従って、制御フローを記述せずに計算のロジックを表現します。クエリは目標指向で自己記述的であるため、分析と理解が容易です。また簡潔です。さらに、LINQを使用すると、データ構造の抽象化に大きく依存します。これには、高いメンテナンス性と再利用性が伴います。
反復アプローチは、命令型パラダイムに対処します。これにより、きめ細かな制御が可能になり、高いパフォーマンスが容易に得られます。コードのデバッグも簡単です。よく構築された反復は、クエリよりも読みやすい場合があります。
パフォーマンスと保守性の間には常にジレンマがあります。そして、通常(パフォーマンスに関する特定の要件がない場合)保守性が優先されます。パフォーマンスの問題がある場合にのみ、アプリケーションのプロファイルを作成し、問題の原因を見つけて、パフォーマンスを改善する必要があります(同時に保守性を低下させることで、そうです、そうです)。
サンプルについて。 Linqは、コードに一致保守性を追加しないため、ここではあまり良い解決策ではありません。実際、私にとっては、単純なループよりも、投影、フィルタリング、および投影がさらに悪いように見えます。ここで必要なのは、単純なArray.IndexOfです。これは、ループよりも保守性が高く、パフォーマンスはほぼ同じです。
Array.IndexOf(array, matchString)
さて、あなたはあなた自身の質問に対する答えを与えました。
最高のパフォーマンスが必要な場合はFor
ループを使用し、読みやすくする場合はLinq
を使用します。
また、インラインラムダ式の恩恵を受ける(したがって、Linqにより近い)Parallel.Foreach()を使用する可能性を念頭に置いてください。
LINQを好む人もいればそうでない人もいると思います。
パフォーマンスが問題である場合、私はあなたのシナリオの両方のコードのプロファイルを作成し、差が無視できる場合は、コードを維持するのはあなたである可能性が高いため、より適合性があると感じるものに進みます。
また、PLINQの使用や、ループの並列実行についても考えましたか?
少しの無回答で、実際には 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テストスイートからのいくつかのEdgeケースに失敗します。また、いくつかの関数を変換する必要があります(現在、corefxのSystem.Linq実装があり、パフォーマンスの観点ではないにしても、APIの観点から互換性があります)。しかし、もう助け、コメントなどをしたい人はいただければ幸いです...
最適なオプションは、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