4

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

パフォーマンスに影響を与えずにクラス自体を列挙可能にする方法はありますか?

通常コード:

//Class
public class Foo {
    //Stuff
    public Item[,] InsideArray {get; private set;}
}

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

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

//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#に「ねえ、このオブジェクトを反復処理するように頼んだら、配列を反復処理するのと同じくらい速い」と伝える方法があることを望んでいましたが、明らかにそれは不可能です...少なくとも提案された回答からこれまで。

4

4 に答える 4

5

特注のイテレータを使用すると、処理が速くなる場合があります(既知のタイプとして返​​されるように編集されています)。

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

(ArrayIteratorをFooのGetEnumeratorとして直接使用します-基本的には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);
    }
}
于 2009-05-17T09:41:48.917 に答える
3

IEnumerable<item>呼び出す前に配列をキャストするGetEnumerator()と、ジェネリックが得られますIEnumerator。例えば:

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

配列を直接列挙するよりもまだ少し遅いかもしれませんがforeach(C# コンパイラは別の方法で行います)、少なくとも邪魔になるものは他にありません。

編集:

さて、あなたは別の試みでインデクサーを使用したと言いました。このアプローチを試すこともできますが、これ以上速くなるとは思いません。

public IEnumerable<Item> Items
{
    get
    {
        foreach (Item x in items)
        {
            yield return x;
        }
    }
}

別の方法は、最初から 2 次元配列を使用しないようにすることです。それは絶対条件ですか?単一の配列を作成した後、どれくらいの頻度で反復していますか? 反復をより安価にするために、作成時にわずかな打撃を与える価値があるかもしれません。

編集: 壁から少し外れている別の提案...イテレータを呼び出し元に戻す代わりに、呼び出し元にデリゲートを使用して各アイテムをどうするかを言わせてみませんか?

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

欠点:

  • アクセスごとにデリゲート呼び出しのペナルティが発生します。
  • (例外をスローする以外に) ループから抜け出すのは困難です。アプローチの仕方は様々ですが、ここまできたら橋を渡りましょう。
  • デリゲートに慣れていない開発者は、少し混乱するかもしれません。
于 2009-05-17T07:30:39.293 に答える
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];
   }
}
于 2009-05-17T07:30:22.413 に答える
-8

すべての形式の反復は安価です。この時代に誰かがなんとかして高価なイテレータを書いて公開した場合、彼らは(当然のことながら)火傷を負うことになります。

時期尚早の最適化は悪です。

乾杯。キース。

于 2009-05-17T07:33:01.037 に答える