2009-05-17 12 views
4

したがって、内部に配列を持つクラスがあります。現在、クラスの項目を列挙するための私の戦略は、コードforeach (item x in classInstance.InsideArray)を使用することです。私はむしろforeach (item x in classInstance)を使い、配列をプライベートにしたいと思います。私の主な関心事は、私は本当に遅いものを避ける必要があるということです。配列は多くヒットします(数百のアイテムがあります)。この配列を列挙することは安価であることが重要です。 1つの考えは、クラスにIEnumerable<item>を実装させることでしたが、InsideArray.getEnumerator()は私に非汎用の列挙子を与えます。私はまた、IEnumerableインターフェイスの実装を試みました。これは働いたが、おそらくボクシングのために非常に遅かった。より速い列挙:配列の列挙を利用する

パフォーマンスヒットなしにクラス自体を列挙できる方法はありますか?

ノーマルコード:

調整、はるかに遅いコード:

//Class 
public class Foo : IEnumerable { 
    //Stuff 
    private Item[,] InsideArray; 
    System.Collections.IEnumerator System.Collections.IEnumerable GetEnumerator() 
    { 
     return InsideArray.GetEnumerator(); 
    } 
} 

//Iteration. Shows up all over the place 
foreach (Item x in classInstance) 
{ 
    //doStuff 
} 

注:非ジェネリックイテレータの実装を追加することができて、私の遅いソリューションよりも高速ですが、それはまだちょうど直接配列を使用するより悪いです。私は何とかC#に "ちょっと、このオブジェクトを反復するように要求すると、それは配列と同じ速さで繰り返すように言いますが、どういうことかはっきりとは分かりません..."と答える方法があることを期待していましたこれまで。クラスにインデクサを追加する方法について

+0

方法あなたのクラスの内部にInsideArrayが実装されていますか? –

+0

パブリックItem [、] InsideArray {get;プライベートセット;} – Brian

+0

あなたが試みた正確なコードを表示するには遅すぎます。 –

答えて

1

方法:

public MyInsideArrayType this[int index] 
{ 
    get{return this.insideArray[index]; 
} 

そして、あなたは本当にforeachの機能が必要な場合:

public IEnumerable<MyInsideArrayType> GetEnumerator() 
{ 
    for(int i = 0; i<this.insideArray.Count;i++) 
    { 
     yield return this[i]; 
    } 
} 
+0

私は既にインデクサーを使用しています。あなたが作った他の提案は非常に遅いです。 – Brian

+3

あなたの心の人を読むことができず、あなたのコードを投稿してください... – BFree

+0

ブライアン:編集した質問に表示された非汎用のIEnumerableソリューションと、この回答に示されているジェネリックフォーム。 –

3

GetEnumerator()を呼び出す前にIEnumerable<item>にあなたの配列をキャストして、あなたはジェネリックを取得しますIEnumerator。たとえば:

string[] names = { "Jon", "Marc" }; 
IEnumerator<string> enumerable = ((IEnumerable<string>)names).GetEnumerator(); 

これはよくまだforeachと直接配列を列挙するよりも少し遅くなることがあります(C#コンパイラは、別の方法でん)が、少なくとも、あなたはある意味では他の何かを持っていません。

編集:

他の試みではインデクサーが使用されています。

代替で開始する2次元配列を使用しないようにしようとするだろう:私はそれがどんな速いだろうとは思わないが、あなたは、このアプローチを試みることができます。それは絶対的な要件ですか? 1つのアレイを作成した後、どれくらいの頻度で反復していますか?反復を安くするために、作成時に少しヒットする価値があります。

EDIT:反復子を呼び出し元に戻すのではなく、壁を少し離れたもう1つの提案です。呼び出し元に代理人を使用して各アイテムの処理方法を教えてください。

public void ForEachItem(Action action) 
{ 
    foreach (Item item in items) 
    { 
     action(item); 
    } 
} 

欠点が:

  • あなたは、各アクセスのデリゲート呼び出しのペナルティを被ります。
  • (例外をスローする以外の方法では)ループから抜け出すのは難しいです。このアプローチにはさまざまな方法がありますが、私たちがそれに来るときにその橋を渡りましょう。
  • デリゲートに精通していない開発者は、少し混乱するかもしれません。
+0

内部配列は2次元であることは言及していません。 – Brian

+0

私ははっきりしていませんでした。私はインデクサーをうまく使っていますが、遅くはありません。しかし、私はforeachループでそれを使用したくありません。 – Brian

+0

私は内側の配列を公開することに腹を立てる必要がありますが、正直言って私はforeachループで不必要なドットを見ても迷惑です。 デリゲートのアイデアは非常に巧妙ですが、アクションを実行するのではなくアイテムを読むためにループが使用されないことが多いため、この特定のプログラムでは実現できません。 – Brian

-5

すべての形式の反復は安価です。この日と年齢の誰かが何とか高価なイテレーターを書いて公開すると、彼らは(正しい)ステークに燃えてしまうでしょう。

早すぎる最適化は悪です。

乾杯。キース。

+2

これは時期尚早の最適化ではありません。列挙子を使用すると、実行時間が30%程度アップします。この時点では、すべてのプログラムが本当に行うことは、データを何度も何度も読み込んでいる間、繰り返し実行されるためです。 – Brian

+0

@Brian、30%は私には「たくさん」のように聞こえますが、C#の空のループに対してさまざまな反復技術のパフォーマンスを測定しようとは決して試みたことがありません...私の経験では、にアクセスモードに適切なデータ構造を使用するなど、他の場所で取得する必要があります。ハーフ・コックに行くための私の考え方。犯罪は意図されていませんでした。乾杯。キース。 – corlettk

+0

30%* IS *が多くあります。しかし、アプリケーションの最も肉のいない部分の反復では、プログラムは一般的に、反復処理を繰り返す各要素から1-2バイトを読み取っており、それらのうちのわずかな部分に1-2バイトを書き込んでいます。ループはとてもシンプルです。時間が経つと、私はこれらのループの結果をたくさんキャッシュすることができましたが、ループは決して完全に消えません。 – Brian

5

オーダーメイドのイテレータは(はとして知られているタイプを返すようにを編集した)、それは速くなるかもしれない:

Basic: 2468ms - -2049509440 
Bespoke: 1087ms - -2049509440 

(あなたがfooのGetEnumeratorメソッドとして直接ArrayIteratorを使用します - 本質的ArrayEnumerator.GetEnumeratorからコードをコピーし、自分のポイントは、コードで

)型指定のイテレータは、インタフェースよりも高速であることを示すことである。

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

class Foo 
{ 
    public struct ArrayIterator<T> : IEnumerator<T> 
    { 
     private int x, y; 
     private readonly int width, height; 
     private T[,] data; 
     public ArrayIterator(T[,] data) 
     { 
      this.data = data; 
      this.width = data.GetLength(0); 
      this.height = data.GetLength(1); 
      x = y = 0; 
     } 
     public void Dispose() { data = null; } 
     public bool MoveNext() 
     { 
      if (++x >= width) 
      { 
       x = 0; 
       y++; 
      } 
      return y < height; 
     } 
     public void Reset() { x = y = 0; } 
     public T Current { get { return data[x, y]; } } 
     object IEnumerator.Current { get { return data[x, y]; } } 
    } 
    public sealed class ArrayEnumerator<T> : IEnumerable<T> 
    { 
     private readonly T[,] arr; 
     public ArrayEnumerator(T[,] arr) { this.arr = arr; } 

     public ArrayIterator<T> GetEnumerator() 
     { 
      return new ArrayIterator<T>(arr); 
     } 

     System.Collections.Generic.IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator() 
     { 
      return GetEnumerator(); 
     } 
     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
     { 
      return GetEnumerator(); 
     } 

    } 
    public int[,] data; 

    public IEnumerable<int> Basic() 
    { 
     foreach (int i in data) yield return i; 
    } 
    public ArrayEnumerator<int> Bespoke() 
    { 
     return new ArrayEnumerator<int>(data); 
    } 
    public Foo() 
    { 
     data = new int[500, 500]; 
     for (int x = 0; x < 500; x++) 
      for (int y = 0; y < 500; y++) 
      { 
       data[x, y] = x + y; 
      } 
    } 
    static void Main() 
    { 
     Test(1); // for JIT 
     Test(500); // for real 
     Console.ReadKey(); // pause 
    } 
    static void Test(int count) 
    { 
     Foo foo = new Foo(); 
     int chk; 
     Stopwatch watch = Stopwatch.StartNew(); 
     chk = 0; 
     for (int i = 0; i < count; i++) 
     { 
      foreach (int j in foo.Basic()) 
      { 
       chk += j; 
      } 
     } 
     watch.Stop(); 
     Console.WriteLine("Basic: " + watch.ElapsedMilliseconds + "ms - " + chk); 

     watch = Stopwatch.StartNew(); 
     chk = 0; 
     for (int i = 0; i < count; i++) 
     { 
      foreach (int j in foo.Bespoke()) 
      { 
       chk += j; 
      } 
     } 
     watch.Stop(); 
     Console.WriteLine("Bespoke: " + watch.ElapsedMilliseconds + "ms - " + chk); 
    } 
} 
+0

これは最初の要素をスキップし、異なる順序で繰り返しますが、どちらも簡単に修正できます。書かれているように、新しいイテレーターを繰り返し作成することになり、私の場合は不要です。 - 私はこれが余分なドットを取り除き、元のバージョンとほぼ同じ速度で走っているという点で、他のどのソリューションよりも優れた基準を満たしていると思います。それは私が望むより少し余分なコードを追加しますが、私はそこに良い解決策がないと思います。 - 私はちょうどそれを吸って、ドットで生きていると思います。私は本当にこのような余分なコードを追加したくないし、私は自分のパフォーマンスでひどいです。 – Brian

+0

あなたが言っているように、オフバイワンなどについては申し訳ありませんが、簡単に修正できます。 「新しい反復子を何度も作成する」 - これは 'GetEnumerator() 'に対する呼び出しごと、つまり' foreach'ごとに実行します。これは並列反復を可能にするために必要です。しかし、イテレータは構造体であるため、これには大きなオブジェクトコストはありません。 struct/iteratorは共通のパターンです(リストなどを参照)。 –

+0

十分です。つまり、foreachを使って配列を列挙するよりもまだ少し遅いです。しかし、特に驚くべきことではない。しかたがない。 – Brian