Самый быстрый способ скопировать KeyedCollection

2

Я работаю над конструктором копии объекта С#, часть которого включает в себя копирование содержимого 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) требует проверки уникальности существующих данных, хотя я знаю, что это происходит из источника, который гарантирует уникальность.

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

Теги:
optimization
collections

5 ответов

3
Лучший ответ

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

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

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

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

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

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

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

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

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 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;                    // *
    }

  }


}
  • 0
    Впечатляет. Здесь редко можно найти такие полные решения. На первый взгляд выглядит хорошо, я проведу тесты и опубликую результаты здесь. В любом случае, спасибо, что приложили столько усилий для этого.
  • 0
    О, и я использую класс для хранения упорядоченного списка с очень быстрым случайным извлечением, что и призвано обеспечить KeyedCollection.
Показать ещё 5 комментариев
3

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


Старый ответ

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

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

Дополнительная информация о MemberwiseClone здесь.

  • 1
    MemberwiseClone () создает поверхностную копию, но KeyCollection, скорее всего, будет содержать массив для хранения элементов. Поэтому MemberwiseClone () просто скопирует ссылку на этот массив, но не сам массив. Но, конечно, вы можете использовать MemberwiseClone () для дублирования массива и всех других необходимых объектов.
  • 0
    Неплохо подмечено. Я также только что понял, что MemberwiseClone - это защищенный метод.
Показать ещё 1 комментарий
0
      /// 
      /// 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, иначе вы ударите исключения, и null будет возвращен.
Вам также необходимо создать свой собственный IDataObject, потому что DataObject не является Serializable:


   [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

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

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

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

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

  • 0
    Это интересный подход, но, насколько я понимаю, главная причина использования неизменяемых объектов заключается в том, что с ними гораздо проще работать концептуально. То есть вам не нужно запоминать (или искать) каждый бит кода, который модифицирует объект для проверки на побочные эффекты и тому подобное. Моя проблема не в сложности, а в скорости копирования. Кажется, что неизменяемые объекты делают больше копий, чем изменяемые. Я думаю, что неизменяемые объекты решают много проблем программирования, но я не думаю, что они решают эту проблему.
  • 0
    Это уменьшает копирование, так как общее состояние является общим, а не копируется. Идея состоит в том, чтобы не копировать, если вы действительно не должны. Вам действительно нужно скопировать ...
0

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню