代码之家  ›  专栏  ›  技术社区  ›  Brian

更快的枚举:利用数组枚举

  •  4
  • Brian  · 技术社区  · 15 年前

    所以,我有一个包含数组的类。目前,我枚举类项的策略是使用代码, foreach (item x in classInstance.InsideArray) . 我宁愿用 foreach (item x in classInstance) 并将阵列设为私有。我的主要担心是我真的需要避免任何缓慢的事情;阵列会受到很多打击(并且有几百个项目)。至关重要的是,在这个数组上枚举是便宜的。一种想法是让类实现 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 回复  |  直到 15 年前
        1
  •  1
  •   BFree    15 年前

    如何向类中添加索引器:

    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];
       }
    }
    
        2
  •  5
  •   Marc Gravell    15 年前

    定制的迭代器可能会使它更快( 编辑 返回已知类型):

    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);
        }
    }
    
        3
  •  3
  •   Jon Skeet    15 年前

    将数组强制转换为 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;
            }
        }
    }
    

    另一种选择是尽量避免从二维数组开始。这是绝对要求吗?创建一个数组后,您多长时间迭代一次?为了使迭代更便宜,在创建时稍微点击一下可能是值得的。

    编辑:另一个建议,稍微偏离墙壁…与其将迭代器传递回调用者,为什么不让调用者使用委托来说明如何处理每个项呢?

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

    缺点:

    • 每次访问都会受到代表调用的惩罚。
    • 很难脱离循环(除了抛出异常)。我们有不同的方法来接近这座桥,但当我们到达那座桥时,让我们穿过它。
    • 不熟悉代理的开发人员可能会有点困惑。
        4
  •  -5
  •   corlettk    15 年前

    所有形式的迭代都很便宜。如果这个时代的任何人能够以某种方式编写和发布一个昂贵的迭代器,他们将(正确地)处于危险之中。

    过早的优化是邪恶的。

    干杯。基思。