Эффективное клонирование кэшированных объектов - PullRequest
3 голосов
/ 29 апреля 2009

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

Вот рабочий процесс:

  1. Элемент данных 1 - текущий элемент в памяти. Этот элемент был первоначально извлечен из кэша и глубоко клонирован (все подобъекты, такие как словари и т. Д.). Затем элемент данных 1 редактируется, а его свойства изменяются.
  2. Затем мы сравниваем этот объект с исходной версией, которая была сохранена в кеше. Поскольку элемент данных 1 был клонирован, а его свойства изменены, эти объекты должны отличаться.

Здесь есть несколько проблем.

Основная проблема в том, что наш метод глубокого клонирования очень дорогой. Мы профилировали его против мелкого клона, и он был в 10 раз медленнее. Это дерьмо. Вот наш метод глубокого клонирования:

    public object Clone()    
    {
        using (var memStream = new MemoryStream())
        {
            var binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
            binaryFormatter.Serialize(memStream, this); 
            memStream.Seek(0, SeekOrigin.Begin);
            return binaryFormatter.Deserialize(memStream);
        }
    }

Мы первоначально использовали следующее для клонирования:

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

Это было более производительно, но из-за мелкого клонирования все сложные объекты, которые были свойствами этого объекта, такие как словари и т. Д., Не были клонированы. Объект по-прежнему будет содержать ту же ссылку, что и объект, находящийся в кэше, поэтому при сравнении свойства будут такими же.

Итак, есть ли у кого-нибудь эффективный способ глубокого клонирования объектов C #, который бы охватывал клонирование всего графа объектов?

Ответы [ 4 ]

6 голосов
/ 29 апреля 2009

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

Я хотел бы рассмотреть возможность использования ICloneable для глубокого копирования и / или IComparable для сравнения, если объекты различны ... если производительность для вас так велика.

1 голос
/ 29 апреля 2009

Возможно, мой ответ может не относиться к вашему делу, потому что я не знаю, каковы ваши ограничения и требования, но я чувствую, что клонирование общего назначения может быть проблематичным. Как вы уже сталкивались, производительность может быть проблемой. Что-то должно идентифицировать уникальные экземпляры в графе объектов и затем создать точную копию. Это то, что двоичный сериализатор делает для вас, но он также делает больше (сама сериализация). Я не удивлен, увидев, что это медленнее, чем вы ожидали. У меня подобный опыт (кстати тоже связанный с кешированием). Мой подход заключается в том, чтобы реализовать клонирование самостоятельно; т.е. реализовать IClonnable для классов, которые на самом деле нужно клонировать. Сколько классов в вашем приложении, которые вы кэшируете? Если их слишком много (для ручного кодирования клонирования), имеет ли смысл рассмотреть некоторые варианты генерации кода?

1 голос
/ 29 апреля 2009

Может, тебе не стоит глубоко клонировать?

Другие опции:

1) Сделайте так, чтобы ваш «кэшированный» объект запомнил его исходное состояние и установите it update «change» флаг каждый раз, когда что-то меняется.

2) Не запоминать исходное состояние и просто помечать объект как грязный, если что-то когда-либо изменилось. Затем перезагрузите объект из исходного источника для сравнения. Бьюсь об заклад, ваши объекты меняются реже, чем не меняются, и еще реже возвращаются к тому же значению.

0 голосов
/ 09 апреля 2013

Глубокое клонирование можно выполнить двумя способами: с помощью реализации ICloneable (и вызова метода Object.MemberwiseClone) или с помощью двоичной сериализации.

Первый путь

Первый (и, возможно, более быстрый, но не всегда лучший) способ заключается в реализации интерфейса ICloneable в каждом типе. Пример ниже иллюстрирует. Класс C реализует ICloneable, и, поскольку этот класс ссылается на другие классы D и E, последние также реализуют этот интерфейс. Внутри метода Clone в C мы вызываем метод Clone других типов.

Public Class C
Implements ICloneable

    Dim a As Integer
    ' Reference-type fields:
    Dim d As D
    Dim e As E

    Private Function Clone() As Object Implements System.ICloneable.Clone
        ' Shallow copy:
        Dim copy As C = CType(Me.MemberwiseClone, C)
        ' Deep copy: Copy the reference types of this object:
        If copy.d IsNot Nothing Then copy.d = CType(d.Clone, D)
        If copy.e IsNot Nothing Then copy.e = CType(e.Clone, E)
        Return copy
    End Function
End Class

Public Class D
Implements ICloneable

    Public Function Clone() As Object Implements System.ICloneable.Clone
        Return Me.MemberwiseClone()
    End Function
End Class

Public Class E
Implements ICloneable

    Public Function Clone() As Object Implements System.ICloneable.Clone
        Return Me.MemberwiseClone()
    End Function
End Class

Теперь, когда вы вызываете метод Clone для экземпляра C, вы получаете глубокое клонирование этого экземпляра:

Dim c1 As New C
Dim c2 As C = CType(c1.Clone, C)   ' Deep cloning.  c1 and c2 point to two different 
                                   ' locations in memory, while their values are the 
                                   ' same at the moment.  Changing a value of one of
                                   ' these objects will NOT affect the other.

Примечание: если классы D и E имеют ссылочные типы, вы должны реализовать их метод Clone, как мы это делали для класса C. И так далее.

Предупреждения: 1-образец выше действителен, пока нет круговой ссылки. Например, если класс C имеет собственную ссылку (например, поле типа C), реализация интерфейса ICloneable не будет легкой, поскольку метод Clone в C может войти в бесконечный цикл.

2-Еще один момент, на который следует обратить внимание, это то, что метод MemberwiseClone является защищенным методом класса Object. Это означает, что вы можете использовать этот метод только из кода класса, как показано выше. Это означает, что вы не можете использовать его для внешних классов.

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

Второй путь

Бинарная сериализация может использоваться для глубокого клонирования без проблем, перечисленных выше (особенно циклическая ссылка). Вот общий метод, который выполняет глубокое клонирование с использованием двоичной сериализации:

Public Class Cloning
    Public Shared Function DeepClone(Of T)(ByVal obj As T) As T
        Using MStrm As New MemoryStream(100)    ' Create a memory stream.
            ' Create a binary formatter:
            Dim BF As New BinaryFormatter(Nothing, New StreamingContext(StreamingContextStates.Clone))

            BF.Serialize(MStrm, obj)    ' Serialize the object into MStrm.
            ' Seek the beginning of the stream, and then deserialize MStrm:
            MStrm.Seek(0, SeekOrigin.Begin)
            Return CType(BF.Deserialize(MStrm), T)
        End Using
    End Function
End Class

Вот как использовать этот метод:

Dim c1 As New C
Dim c2 As C = Cloning.DeepClone(Of C)(c1)   ' Deep cloning of c1 into c2.  No need to 
                                            ' worry about circular references!
...