管理数百个类而不创建和销毁它们?

时间:2019-05-12 01:14:30

标签: c# performance class memory

我有一个可以处理数百或数千个类的类A,例如,每个类都有一个带有一些计算的方法。 A类具有一种方法,可以从中选择运行数百种或数千种中的哪一类。而且A类的方法在很短的时间内运行了很多次。

我一开始想的解决方案是已经在类A中创建了类,以避免每次执行事件时都必须创建和销毁类,并且垃圾回收器会消耗CPU。但是,正如我说的那样,此类A类将要运行成百上千个类,并且全部加载它们在内存上的开销太高(我认为)。

我的问题是,您能想到一种处理数百或数千个类的最佳方法,这种类每秒运行一些类,而不必在每次使用它们的方法的执行中都创建和销毁它吗?

编辑:

第一个示例:创建并保存类,然后使用它们,我认为这将导致内存消耗。但是,请避免垃圾收集器工作过多。

public class ClassA {

Class1 class1;
Class2 class2;

// ... more classes

Class100 class100;

public ClassA() {
    class1 = new Class1();

    // ... ‎initializations

    class100 = new Class100();
}

public ChooseClass(int numberClass) {

    switch (numberClass) {
        case 1:
            class1.calculate();
            break;
        case 2:
            class2.run();
            break;

            // ... more cases, one for each class

        case 100:
            class100.method();
            break;
        default:
            break;
    }   
}     

}

第二个示例:在使用类时创建类,可以节省内存,但是垃圾收集器会占用大量CPU。

public class ClassA {

public ChooseClass(int numberClass) {

    switch (numberClass) {
        case 1:
            Class1 class1 = new Class1();
            class1.calculate();
            break;
        case 2:
            Class2 Class2 = new Class2();
            class2.run();
            break;

            // ... more cases, one for each class

        case 100:
            Class100 class100 = new Class100();
            class100.method();
            break;
        default:
            break;
    }   
}     

}

1 个答案:

答案 0 :(得分:0)

当您开始增加类实例的数量时,您面临的基本问题是在垃圾回收操作期间都需要对它们全部进行说明和跟踪,即使您从未释放过这些实例,垃圾回收器仍然需要跟踪他们。当程序花费更多的时间执行垃圾收集而不是实际工作时,就产生了一点。我们在二进制搜索树中遇到了这种性能问题,该搜索树最终包含最初是类实例的数百万个节点。

我们可以通过使用List<T>的结构而不是类来绕过这个问题。 (列表的内存由数组支持,对于结构体,垃圾收集器仅需要跟踪对该数组的单个引用即可。)。现在,代替对类的引用,我们将索引存储到此列表,以便访问所需的结构实例。

事实上,我们还遇到了(注意.NET框架的新版本消除了此限制)的问题,即即使在64位下,支持阵列也无法增长到2GB以上。在多个列表上拆分存储(256),并使用32位索引,其中8位用作列表选择器,其余24位用作列表的索引。

当然,建立一个抽象所有这些细节的类很方便,并且需要注意的是,在修改结构时,实际上需要将其复制到本地实例,进行修改,然后将原始结构替换为修改后实例的副本,否则您的更改将在结构的临时副本中发生,并且不会反映在数据集合中。 还有一个性能影响,幸运的是,一旦收集足够大,垃圾回收周期就会非常快,就可以收回投资。

这里有一些代码(很旧),显示了这些想法,并且仅通过将搜索树迁移到这种方法就从服务器上花费了将近100%的CPU时间,到大约15%。 / strong>

public class SplitList<T> where T : struct {
      // A virtual list divided into several sublists, removing the 2GB capacity limit 

      private List<T>[] _lists;
      private Queue<int> _free = new Queue<int>();
      private int _maxId = 0;
      private const int _hashingBits = 8;
      private const int _listSelector = 32 - _hashingBits;
      private const int _subIndexMask = (1 << _listSelector) - 1;



      public SplitList() {
         int listCount = 1 << _hashingBits;
         _lists = new List<T>[listCount];
         for( int i = 0; i < listCount; i++ )
            _lists[i] = new List<T>();
      }

      // Access a struct by index
      // Remember that this returns a local copy of the struct, so if changes are to be done, 
      // the local copy must be copied to a local struct,  modify it, and then copy back the changes
      // to the list
      public T this[int idx] {
         get {
            return _lists[(idx >> _listSelector)][idx & _subIndexMask];
         }
         set {
            _lists[idx >> _listSelector][idx & _subIndexMask] = value ;
         }
      }


      // returns an index to a "new" struct inside the collection
      public int New() {

         int result;
         T newElement = new T();

         // are there any free indexes available? 
         if( _free.Count > 0 ) {
            // yes, return a free index and initialize reused struct to default values
            result = _free.Dequeue();
            this[result] = newElement;
         } else {
            // no, grow the capacity
            result = ++_maxId;
            List<T> list = _lists[result >> _listSelector];
            list.Add(newElement);
         }
         return result;
      }

      // free an index and allow the struct slot to be reused.
      public void Free(int idx) {
         _free.Enqueue(idx);
      }

   }

以下是使用此SplitList支持容器类来查找我们的二叉树实现方式的摘要:

 public class CLookupTree {

      public struct TreeNode {
         public int HashValue;
         public int LeftIdx;
         public int RightIdx;
         public int firstSpotIdx;
      }


      SplitList<TreeNode> _nodes;

      …

     private int RotateLeft(int idx) {
        // Performs a tree rotation to the left, here you can see how we need
        // to retrieve the struct to a local copy (thisNode), modify it, and 
        // push back the modifications to the node storage list

        // Also note that we are working with indexes rather than references to 
        // the nodes
        TreeNode thisNode = _nodes[idx];
        int result = thisNode.RightIdx;
        TreeNode rightNode = _nodes[result];
        thisNode.RightIdx = rightNode.LeftIdx;
        rightNode.LeftIdx = idx;
        _nodes[idx] = thisNode;
        _nodes[result] = rightNode;
        return result;      
      }
   }