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


8

バックグラウンド

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

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

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

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

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

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

質問

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


2
百科事典全体を1つのテキストファイルに収めた愚か者に尋ねます。
JeffO 2012

4
それは測定されるべきものの一つです。2つまたは3つの実装を構築し、パフォーマンスを記録します。LINQまたは手法Xに関する一般化は役に立ちません。LINQを使用して「間違った」答えを宣言することは、インタビュアーには不十分だと思います。負荷が高い場合でも、サーバー側の処理はミリ秒ごとにカウントされます。
Tydus卿

1
「オブジェクトとループに対するlinqのパフォーマンステスト」の簡単なグーグルは、かなりの数のヒットを見つけました。一部には、使用時に自分でテストできるソースコードが含まれています。参照してください。これこれこれを
2012

1
インタビューについては、.NETライブラリーを使用するのではなく、ホイールを再発明する必要があると考える「古い」C ++プログラマーがいることを覚えておいてください。また、LINQやEFを使用するのではなく、すべてのデータアクセスコードを手作業で実行したい古い学校のVBに遭遇することもあります。
jfrankcarr

1
奇妙なことに、あなたが提供したリンクのそれらの例は非常に間違っています。コメントですべての詳細を説明することはできませんが、2番目のリンクを使用します。これは、「foreach x if x = toFind stop」と「select * from list where x like toFind」と同等のlinqクエリを比較します。違いは、最初のインスタンスが見つかったときに最初に停止し、linqクエリは常に繰り返しますすべてのエントリ。検索パターンに一致するすべてのアイテムのコレクションを返します。非常に異なります。それは、LINQが壊れているからではなく、彼が間違ったクエリを使用したためです。
イアン

回答:


9

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

素朴な実装で.GroupBy(e => e)は、すべての単語のコピーがソースマテリアルに保存され、最終的なルックアップは元のソースマテリアルとほぼ同じ大きさになります。値を.GroupBy(e => e, e => null)予測していても、小さな値の大きなルックアップを作成しています。

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

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

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

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

すべての良い点クリス、ありがとう。質問はより一般的であり、本質的に上記のコメントでOdedが回答しているため、少し受け入れることは控えます。私は彼に最初に答えを提供する機会を与えたいと思っています。しかし、あなたの洞察に感謝しますが、それは素晴らしいことです。
Matt Cashatt

6

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

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は読みやすく、特に興味深い予測とグループ化の導入を開始する場合に便利です。読みやすいコードは、勝利であるコードを維持するのが簡単です。

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


全体的には同意しますが、Wh​​ereメソッドの動作-Whereメソッドは一致するすべてのアイテムをコレクションに追加しません。式ツリー内のアイテムをフィルタリングするために必要な情報を格納します。返されたイテレータが実際に使用されていない場合、フィルタリングはまったく行われません。
Codism

優れた点、私はそれを述べるべきだった。彼らの例では、返されたイテレータを使用しています。これは彼らのテストの狂気でした。見つかった1つの値を抽出するために(テストデータのすべての項目が一意でした)、結果を列挙するために結果の列挙型を反復するforeachがありました。もちろん、結果は1つしかなかったので、1つの回答しか出力されませんでした。狂気:)
Ian

私はLINQを使用していませんCountが、カプセル化でうまく機能しないいくつかの狭いシナリオのようなものを最適化することは不愉快だと思います。100万項目のリストと4項目の反復子を連結すると、Count約5つの操作が必要になりますが、代わりに100万が必要になります。私は、MSが定義したいIEnhancedEnumeratorint Move(int)成功の場合は0を返すか、(そうする障害が発生した場合に不足分の金額を返すメソッドMove(1000003)新しく作成された上でList<T>.Enumerator3を返します百万項目のリストから)。任意の実装...
スーパーキャット2015

... of IEnumerable<T>はの実装でラップできますがIEnhancedEnumeratorIEnhancedEnumerator直接実装するタイプでは、多くの操作で桁違いの高速化が可能になり、returnのようなものでもAppend、構成要素の高速シーク機能を公開できます。
スーパーキャット2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.