web-dev-qa-db-ja.com

LINQは、低レベルのデータ反復手法よりもはるかに多くの処理サイクルとメモリを必要としますか?

バックグラウンド

私は最近、.NETスタックを使用する役職に対する厳しい技術面接に耐える過程にあります この中には、このような愚かな質問が含まれています 、およびより有効な質問があります。最近、有効な問題に遭遇しましたが、こちらのコミュニティで確認したいと思います。

面接官から、テキストドキュメント内の単語の頻度を数え、結果をランク付けする方法を尋ねられたとき、私は

  1. ストリームオブジェクトを使用して、テキストファイルを文字列としてメモリに配置します。
  2. 句読点を無視しながら、文字列をスペース上の配列に分割します。
  3. 配列に対してLINQを使用して.GroupBy()および.Count()に変換し、次にOrderBy()でカウントを示します。

私はこの答えを2つの理由で間違っていました:

  1. テキストファイル全体をメモリにストリーミングすると、障害が発生する可能性があります。それが完全な百科事典だったらどうでしょうか?代わりに、一度に1つのブロックをストリーミングして、ハッシュテーブルの作成を開始します。
  2. LINQは高すぎるため、必要な処理サイクルが多すぎます。代わりにハッシュテーブルを作成する必要がありました。反復ごとに、Wordがハッシュテーブルに追加されたのは、それが他に存在しなかった場合にのみ、そのカウントをインクリメントしました。

最初の理由は、まあ、合理的なようです。しかし、2番目は、より多くの休止を私に与えます。 LINQのセールスポイントの1つは、ハッシュテーブルのような下位レベルの操作を単純に抽象化することですが、ベールの下では、同じ実装です。

質問

抽象化されたメソッドを呼び出すためのいくつかの追加の処理サイクルに加えて、LINQは、特定のデータ反復タスクを実行するために、低レベルよりも大幅により多くの処理サイクルを必要としますか?タスク(ハッシュテーブルの作成など)はどうでしょうか。

8
Matt Cashatt

この回答の主な弱点は、Linqの使用が少なく、特定の演算子が選択されていることです。 GroupByは各要素を受け取り、それをキーと値に射影し、ルックアップに入ります。つまり、すべてのWordがルックアップに何かを追加します。

素朴な実装.GroupBy(e => e)は、すべてのWordのコピーをソースマテリアルに格納し、最終的なルックアップを元のソースマテリアルとほぼ同じ大きさにします。 .GroupBy(e => e, e => null)を使用して値を投影しても、小さな値の大きなルックアップを作成しています。

ここで必要なのは、必要な情報のみを保存する演算子です。これは、各Wordの1つのコピーと、それまでのそのWordの数です。そのためには、Aggregateを使用できます。

words.Aggregate(new Dictionary<string, int>(), (counts, Word) => 
{
    int currentCount;
    counts.TryGetValue(Word, currentCount);
    counts[Word] = currentCount + 1;
    return counts;
} 

ここから、これを速くするためにいくつかの方法があります。

  1. 分割中に多数の文字列を作成する代わりに、元の文字列とWordを含むセグメントを参照する構造体を渡し、一意のキーであることが判明した場合にのみセグメントをコピーすることができます
  2. Parallel Linqを使用して 複数のコアに集約し、結果を結合します 。これは、これを手作業で行うために必要な脚の作業と比較すると、些細なことです。
9
Chris Pitman

面接官は彼が何を話しているのか本当に知りませんでした。さらに悪いことに、彼は「正しい」答えがあると信じています。彼があなたが働きたい人であったなら、私は彼があなたの最初の答えを取り、あなたがそれを選んだ理由を見つけ、それから彼がそれに問題を見つけることができるならそれを改善するようにあなたに挑戦することを期待します。

LINQは魔法のように見えるので、人々を怖がらせます。それは実際には非常に単純です(非常に単純なので、それを思いつくには天才である必要があります)

var result = from item in collection where item=>item.Property > 3 select item;

にコンパイルされます:

IEnumerable<itemType> result = collection.Where(item=>item.property >3);

(もし私が構文を間違えた場合は、叫ばないでください。それは真夜中過ぎで、ベッドにいます:))

ラムダを使用するIEnumerableの拡張メソッドはどこにありますか。ラムダは単に(この場合)デリゲートにコンパイルされます。

bool AMethod(ItemType item)
{
    return item.property >3;
}

Whereメソッドは、AMethodがtrueを返す項目のすべてのインスタンスを、返されるコレクションに追加するだけです。

Foreachを実行して、ループ内のすべての一致するアイテムをコレクションに追加するよりも遅い理由はありません。実際には、Where拡張メソッドがおそらくまさにそれを行っています。真の魔法は、代替のwhere基準を注入する必要がある場合に発生します。

上記のコメントで述べたように、リンクされている例のいくつかは非常に間違っています。そして、問題を引き起こすのはこの種の誤報です。

最後に、面接でチャンスが与えられた場合、次のように言うことができます。

  • LINQは読みやすく、特に興味深い予測とグループ化の導入を開始する場合に便利です。読みやすいコードは、勝つコードを維持するのが簡単です。

  • それが実際にボトルネックである場合、パフォーマンスを測定し、それを別のものに置き換えることは本当に簡単です。

6
Ian