Производительность DeepClone (с использованием двоичной сериализации) и ручная настройка свойств - PullRequest
8 голосов
/ 23 февраля 2012

У меня есть коллекция объектов, и я пытаюсь клонировать эту коллекцию и пытаюсь понять влияние различных подходов на производительность.

Объект в коллекции имеет около 20 свойств: все строки, целые числа, числа с плавающей запятой (у этих объектов нет вложенных объектов внутри него). Два подхода:

  1. Создание метода DeepClone ():

    public static class ExtensionMethods
    {
        public static T DeepClone<T>(this T a)
        {
           using (var stream = new MemoryStream())
           {
               var formatter = new BinaryFormatter();
               formatter.Serialize(stream, a);
               stream.Position = 0;
              return (T)formatter.Deserialize(stream);
           }
       }
    

    }

  2. Вручную написать код «copy», где я перебираю коллекцию и «создаю» новый объект, а затем вручную устанавливаю все 20 свойств. как то так

     public MyObject Copy(MyObject myObj)
    {
     var obj = new MyObject();
     obj.Prop1 = myObj.Prop1;
     obj.Prop2 = myObj.Prop2;
     return obj;
    

    }

Я получаю очень противоречивые результаты, поэтому я хотел бы узнать мнение людей о:

  1. Должен ли один быть намного быстрее другого? Я бы подумал о втором варианте, но мои тесты, кажется, не подтверждают это, поэтому я пытаюсь выяснить, делаю ли я что-то неправильно.

  2. Есть ли способ сделать это еще быстрее?

Ответы [ 4 ]

5 голосов
/ 23 февраля 2012

Ну, во-первых, маршрут BinaryFormatter определенно должен быть медленнее, поскольку он использует отражение для получения / установки свойств. Наиболее распространенным методом является использование интерфейса IClonable в сочетании с конструктором копирования.

class A : ICloneable
{
    private readonly int _member;

    public A(int member)
    {
        _member = member;
    }

    public A(A a)
    {
        _member = a._member;
    }

    public object Clone()
    {
        return new A(this);
    }
}

Конечно, строго говоря, вам нужен только конструктор копирования, который должен быть самым быстрым методом. Если ваши объекты просты, попробуйте использовать встроенную функцию MemberwiseClone .

class A : ICloneable
{
    private readonly int _member;

    public A(int member)
    {
        _member = member;
    }

    public object Clone()
    {
        return MemberwiseClone();
    }
}

Тем временем я написал некоторый тестовый код , чтобы увидеть, был ли MemberwiseClone () значительно быстрее или медленнее, чем при использовании конструктора копирования. Вы можете найти это здесь . Я обнаружил, что MemberwiseClone на самом деле намного медленнее, чем выполнение CopyConstructor, по крайней мере, для небольших классов. Обратите внимание, что использование BinaryFormatter очень медленно.

2 голосов
/ 23 февраля 2012

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

Мы провели некоторый подробный бенчмаркинг и обнаружили, что настройка свойства всегда была по крайней мере на порядок быстрее , чем подход BinaryFormatter, хотя, очевидно, требовала ручную реализацию в отличие от гораздо более простого BinaryFormatter подход. Для глубинных графов объектов разница стала более выраженной IIRC.

В итоге мы остановились на трехэтапном подходе к «клонированию»:

  • если бы тип был неизменным (что мы обозначали бы с помощью интерфейса маркера, IImutable, но вы могли бы в равной степени использовать атрибут или что-то подобное), мы бы "клонировали", возвращая исходный экземпляр. Поскольку мы знали, что никто не может изменить его, было безопасно возвращать тот же экземпляр. Очевидно, это был самый быстрый тип «клона», хотя он явно не был клоном вообще.
  • Если бы тип реализовал наш собственный интерфейс IDeepCloneable<T> (который был бы похож на ваш второй пример - но общий), мы бы использовали это. [Этот универсальный интерфейс наследовал бы от неуниверсального эквивалента IDeepCloneable]
  • Если это не удастся, мы вернемся к вашему первому примеру, BinaryFormatter.

Я упоминаю «неизменный» подход, потому что в зависимости от того, что вы делаете, иногда лучшим способом является перепроектирование классов, которые вам нужно клонировать, чтобы их вообще не нужно было клонировать. Если они созданы только для чтения, это легко; но даже если это не так, иногда подход «строитель / неизменяемый тип» полезен (см. Uri против UriBuilder в рамках. Первый по существу неизменен, в то время как последний может использоваться для создания и / или изменения экземпляров объекта. бывший).

0 голосов
/ 23 февраля 2012

Сериализация определенно медленнее, чем просто присвоение свойств.

Двоичная сериализация предпочтительнее, потому что код прост и удобен в обслуживании.

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

0 голосов
/ 23 февраля 2012

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

Можете ли вы воспроизвести случай, когда использование сериализации было быстрее, чем копирование свойств «вручную»?

...