Самый быстрый способ скопировать KeyedCollection - PullRequest
2 голосов
/ 12 июня 2009

Я работаю над конструктором копирования объектов C #, частью которого является копирование содержимого KeyedCollection в новый KeyedCollection. Это то, что я реализовал в настоящее время:

class MyKeyedCollection : KeyedCollection<uint, DataObject>
{
    protected override uint GetKeyForItem( DataObject do )
    {
        return do.Key;
    }
}

class MyObject
{
    private MyKeyedCollection kc;

    // Copy constructor
    public MyObject( MyObject that )
    {
        this.kc = new MyKeyedCollection();
        foreach ( DataObject do in that.kc )
        {
            this.kc.Add( do );
        }
    }
}

Это правильно: коллекция копируется, как и ожидалось. Проблема в том, что это тоже немного медленно. Я предполагаю, что проблема в том, что каждый .Add (do) требует проверки уникальности существующих данных, хотя я знаю, что они поступают из источника, который гарантирует уникальность.

Как я могу сделать этот конструктор копирования максимально быстрым?

Ответы [ 5 ]

3 голосов
/ 13 июня 2009

Хорошо, а как насчет решения с небольшим небезопасным кодом? Просто для удовольствия?

ВНИМАНИЕ! Это кодируется для ОС Windows и 32-разрядных, но нет причины, по которой этот метод не может быть изменен для работы на 64-разрядных или других ОС. Наконец, я проверил это на 3,5 рамки. Я думаю, что это будет работать на 2.0 и 3.0, но я не тестировал. Если Редмонд изменит количество, тип или порядок переменных экземпляра между ревизиями или исправлениями, это не будет работать.

Но это быстро !!!

Это взламывает KeyedCollection, лежащие в его основе List <> и Dictionary <> и копирует все внутренние данные и свойства. Это хак, потому что для этого вам нужен доступ к закрытым внутренним переменным. Я в основном сделал некоторые структуры для KeyedCollection, List и Dictionary, которые являются частными переменными этих классов в правильном порядке. Я просто указываю на эти структуры, где находятся классы и вуаля ... вы можете связываться с приватными переменными !! Я использовал отражатель RedGate, чтобы увидеть, что делает весь код, чтобы я мог понять, что копировать. Тогда нужно просто скопировать некоторые типы значений и использовать Array.Copy в нескольких местах.

Результат: CopyKeyedCollection <,>, CopyDict <> и CopyList <>. Вы получаете функцию, которая может быстро скопировать словарь <>, и функцию, которая может быстро скопировать список <> бесплатно!

Одна вещь, которую я заметил при разработке всего этого, это то, что KeyedCollection содержит список и словарь, указывающие на одни и те же данные! Сначала я думал, что это расточительно, но комментаторы отметили, что KeyedCollection специально для случая, когда вам нужен упорядоченный список и словарь одновременно.

В любом случае, я программист на ассемблере / c, который некоторое время был вынужден использовать vb, поэтому я не боюсь делать подобные хаки. Я новичок в C #, поэтому скажите мне, если я нарушил какие-либо правила или если вы думаете, что это круто.

Кстати, я исследовал сборку мусора, и с GC это должно работать нормально. Я думаю, что было бы разумно, если бы я добавил немного кода, чтобы исправить немного памяти для мс, которые мы тратим на копирование. Вы, ребята, скажите мне. Я добавлю несколько комментариев, если кто-нибудь попросит их.

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections.ObjectModel;
using System.Reflection;

namespace CopyCollection {

  class CFoo {
    public int Key;
    public string Name;
  }

  class MyKeyedCollection : KeyedCollection<int, CFoo> {
    public MyKeyedCollection() : base(null, 10) { }
    protected override int GetKeyForItem(CFoo foo) {
      return foo.Key;
    }
  }

  class MyObject {
    public MyKeyedCollection kc;

    // Copy constructor
    public MyObject(MyObject that) {
      this.kc = new MyKeyedCollection();
      if (that != null) {
        CollectionTools.CopyKeyedCollection<int, CFoo>(that.kc, this.kc);
      }
    }
  }

  class Program {

    static void Main(string[] args) {

      MyObject mobj1 = new MyObject(null);
      for (int i = 0; i < 7; ++i)
        mobj1.kc.Add(new CFoo() { Key = i, Name = i.ToString() });
      // Copy mobj1
      MyObject mobj2 = new MyObject(mobj1);
      // add a bunch more items to mobj2
      for (int i = 8; i < 712324; ++i)
        mobj2.kc.Add(new CFoo() { Key = i, Name = i.ToString() });
      // copy mobj2
      MyObject mobj3 = new MyObject(mobj2);
      // put a breakpoint after here, and look at mobj's and see that it worked!
      // you can delete stuff out of mobj1 or mobj2 and see the items still in mobj3,
    }
  }

  public static class CollectionTools {

    public unsafe static KeyedCollection<TKey, TValue> CopyKeyedCollection<TKey, TValue>(
     KeyedCollection<TKey, TValue> src, 
     KeyedCollection<TKey, TValue> dst) {

      object osrc = src;
      // pointer to a structure that is a template for the instance variables 
      // of KeyedCollection<TKey, TValue>
      TKeyedCollection* psrc = (TKeyedCollection*)(*((int*)&psrc + 1));  
      object odst = dst;
      TKeyedCollection* pdst = (TKeyedCollection*)(*((int*)&pdst + 1));
      object srcObj = null;
      object dstObj = null;
      int* i = (int*)&i;  // helps me find the stack

      i[2] = (int)psrc->_01_items;
      dstObj = CopyList<TValue>(srcObj as List<TValue>);
      pdst->_01_items = (uint)i[1];

      // there is no dictionary if the # items < threshold
      if (psrc->_04_dict != 0) {
        i[2] = (int)psrc->_04_dict;
        dstObj = CopyDict<TKey, TValue>(srcObj as Dictionary<TKey, TValue>);
        pdst->_04_dict = (uint)i[1];
      }

      pdst->_03_comparer = psrc->_03_comparer;
      pdst->_05_keyCount = psrc->_05_keyCount;
      pdst->_06_threshold = psrc->_06_threshold;
      return dst;
    }

    public unsafe static List<TValue> CopyList<TValue>(
     List<TValue> src) {

      object osrc = src;
      // pointer to a structure that is a template for 
      // the instance variables of List<>
      TList* psrc = (TList*)(*((int*)&psrc + 1));  
      object srcArray = null;
      object dstArray = null;
      int* i = (int*)&i;  // helps me find things on stack

      i[2] = (int)psrc->_01_items;
      int capacity = (srcArray as Array).Length;
      List<TValue> dst = new List<TValue>(capacity);
      TList* pdst = (TList*)(*((int*)&pdst + 1));
      i[1] = (int)pdst->_01_items;
      Array.Copy(srcArray as Array, dstArray as Array, capacity);

      pdst->_03_size = psrc->_03_size;

      return dst;
    }

    public unsafe static Dictionary<TKey, TValue> CopyDict<TKey, TValue>(
     Dictionary<TKey, TValue> src) {

      object osrc = src;
      // pointer to a structure that is a template for the instance 
      // variables of Dictionary<TKey, TValue>
      TDictionary* psrc = (TDictionary*)(*((int*)&psrc + 1)); 
      object srcArray = null;
      object dstArray = null;
      int* i = (int*)&i;  // helps me find the stack

      i[2] = (int)psrc->_01_buckets;
      int capacity = (srcArray as Array).Length;
      Dictionary<TKey, TValue> dst = new Dictionary<TKey, TValue>(capacity);
      TDictionary* pdst = (TDictionary*)(*((int*)&pdst + 1));
      i[1] = (int)pdst->_01_buckets;
      Array.Copy(srcArray as Array, dstArray as Array, capacity);

      i[2] = (int)psrc->_02_entries;
      i[1] = (int)pdst->_02_entries;
      Array.Copy(srcArray as Array, dstArray as Array, capacity);

      pdst->_03_comparer = psrc->_03_comparer;
      pdst->_04_m_siInfo = psrc->_04_m_siInfo;
      pdst->_08_count = psrc->_08_count;
      pdst->_10_freeList = psrc->_10_freeList;
      pdst->_11_freeCount = psrc->_11_freeCount;

      return dst;
    }

    // these are the structs that map to the private variables in the classes
    // i use uint for classes, since they are just pointers
    // statics and constants are not in the instance data.
    // I used the memory dump of visual studio to get these mapped right.
    // everything with a * I copy.  I Used RedGate reflector to look through all
    // the code to decide what needed to be copied.
    struct TKeyedCollection {
      public uint _00_MethodInfo;                  // pointer to cool type info
      // Collection
      public uint _01_items;                       // * IList<T>
      public uint _02_syncRoot;                    //   object
      // KeyedCollection
      public uint _03_comparer;                    //   IEqualityComparer<TKey> 
      public uint _04_dict;                        // * Dictionary<TKey, TItem> 
      public int _05_keyCount;                     // *
      public int _06_threshold;                    // *
      // const int defaultThreshold = 0;
    }

    struct TList {
      public uint _00_MethodInfo;                   //
      public uint _01_items;                        // * T[] 
      public uint _02_syncRoot;                     //   object
      public int _03_size;                          // *
      public int _04_version;                       //
    }

    struct TDictionary {
      // Fields
      public uint _00_MethodInfo;                   //
      public uint _01_buckets;                     // * int[] 
      public uint _02_entries;                     // * Entry<TKey, TValue>[] 
      public uint _03_comparer;                    //   IEqualityComparer<TKey> 
      public uint _04_m_siInfo;                    //   SerializationInfo
      public uint _05__syncRoot;                   //   object 
      public uint _06_keys;                        //   KeyCollection<TKey, TValue> 
      public uint _07_values;                      //   ValueCollection<TKey, TValue> 
      public int _08_count;                        // *
      public int _09_version;
      public int _10_freeList;                     // * 
      public int _11_freeCount;                    // *
    }

  }


}
3 голосов
/ 12 июня 2009

Я только что выполнил тест, добавив 10 000 000 элементов в различные коллекции, и KeyedCollection занял примерно 7x длины списка, но только примерно на 50% длиннее объекта Dictionary. Учитывая, что KeyedCollection является комбинацией этих двух, производительность Add вполне разумна, и проверка дублирующегося ключа, которую он выполняет, явно не занимает , что много времени. Возможно, вы захотите запустить аналогичный тест на KeyedCollection, и, если он будет значительно медленнее, вы можете начать искать в другом месте (проверьте свой получатель MyObject.Key, чтобы убедиться, что вы не получаете накладных расходов от этого).


Старый ответ

Вы пробовали:

this.kc = that.kc.MemberwiseClone() as MyKeyedCollection;

Подробнее о MemberwiseClone здесь .

0 голосов
/ 18 июня 2009
      /// 
      /// Clones Any Object.
      /// &lt/summary>
      /// &ltparam name="objectToClone">The object to clone.&lt/param>
      /// &ltreturn>The Clone&lt/returns>
      public static T Clone&ltT&gt(T objectToClone)
      {
         T cloned_obj = default(T);
         if ((!Object.ReferenceEquals(objectToClone, null)) && (typeof(T).IsSerializable))
         {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bin_formatter = null;
            Byte[] obj_bytes = null;

            using (MemoryStream memory_stream = new MemoryStream(1000))
            {
               bin_formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
               try
               {
                  bin_formatter.Serialize(memory_stream, objectToClone);
               }
               catch (SerializationException) { }
               obj_bytes = memory_stream.ToArray();
            }

            using (MemoryStream memory_stream = new MemoryStream(obj_bytes))
            {
               try
               {
                  cloned_obj = (T)bin_formatter.Deserialize(memory_stream);
               }
               catch (SerializationException) { }
            }
         }
         return cloned_obj;
      }

Примечание: objectToClone должен быть Serializable, в противном случае вы попадете в исключения, и будет возвращен ноль.
Вам также необходимо создать собственный IDataObject, поскольку DataObject не может быть сериализуемым:


   [Serializable]
   public class MyDataObject : IDataObject
   {
      public int mData;

      public MyDataObject(int data)
      {
         mData = data;
      }

      #region IDataObject Members

      public object GetData(Type format)
      {
         return mData;
      }

      

      #endregion
   }
0 голосов
/ 12 июня 2009

Если вы делаете это много, это говорит о том, что вы должны вместо этого использовать неизменные коллекции.

Это структуры, которые вы не изменяете напрямую, а вместо этого «модификации» возвращают новый объект, который может использовать состояние старых объектов, но отражать сделанные вами изменения.

Для .Net доступны различные неизменяемые словари / наборы / древовидные карты (многие в f #, однако, так как он более поддается этому стилю разработки)

У Эрика Липперта есть несколько статей по этому , и AVL Tree должно быть точно таким, как вы хотите.

0 голосов
/ 12 июня 2009

Вы могли бы попытаться сериализовать объект, а затем десериализовать его в новый объект - я не могу сказать, получит ли это какую-либо производительность, но может.

...