2012-02-23 14 views
8

Mam kolekcję obiektów i próbuję sklonować tę kolekcję i próbować zrozumieć wpływ różnych podejść na wydajność.Wydajność DeepClone (przy użyciu serializacji binarnej) a ręczne ustawianie właściwości

Obiekt w kolekcji ma około 20 właściwości wszystkich ciągów, int, float (ten obiekt nie zawiera żadnych obiektów zagnieżdżonych). Oba podejścia są:

  1. Tworzenie DeepClone() metoda:

    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. ręcznie napisać "kopia" kodu, gdzie jestem zapętlenie poprzez zbieranie i "nowych" ing nowy obiekt, a następnie ręcznie ustawiając wszystkie z 20 właściwości. coś takiego

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

    }

Dostaję bardzo niespójne wyniki, więc chciałem się narodów opinię na temat:

  1. Trzeba być znacznie szybciej niż inne? Pomyślałbym o wyborze drugim, ale moje testy nie wspierają tego, więc próbuję ustalić, czy robię coś złego.

  2. Czy można to zrobić jeszcze szybciej?

+0

Nie jestem pewien, dlaczego nazwałeś ten DeepClone(), gdy chcesz tylko wykonać płytkie kopiowanie. Ponieważ ciągi są niezmienne, płytka kopia napisu jest odpowiednikiem głębokiej kopii. Ponadto ręczne ustawianie musi być znacznie szybsze, ponieważ BinaryFormatter używa odbicia, aby uzyskać nazwy właściwości, setery, konstruktory itp. – Gleno

+0

@Gelno - ta metoda pogłębiłaby Klonowanie obiektu, gdyby zawierał w nim zagnieżdżone obiekty (stąd nazwa) – leora

Odpowiedz

5

Cóż, przede wszystkim trasy BinaryFormatter musi być zdecydowanie wolniej, ponieważ używa refleksji get/set właściwości. Najpopularniejszą metodą jest używanie interfejsu IClonable w połączeniu z konstruktorem kopiującym.

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); 
    } 
} 

Oczywiście ściśle mówiąc trzeba tylko konstruktor kopiujący, który powinien być najszybszy sposób. Jeśli twoje obiekty są proste, powinieneś spróbować użyć wbudowanej funkcji MemberwiseClone.

class A : ICloneable 
{ 
    private readonly int _member; 

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

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

międzyczasie napisałem some test code aby zobaczyć czy MemberwiseClone() był poważnie szybciej lub wolniej niż przy użyciu konstruktora kopii. Możesz go znaleźć here. Zauważyłem, że MemberwiseClone jest rzeczywiście znacznie wolniejszy niż wykonanie CopyConstructor, przynajmniej na małych klasach. Zauważ, że używanie BinaryFormatter jest niesamowicie wolne.

+0

Doesn Czy odbicie używają MemberwiseClone? A może to tylko pamiątka, a GC nie potrzebuje żadnej dodatkowej pomocy? – zmbq

+0

Myślę, że używa niektórych haków CLR, aby uzyskać wydajność, więc w zasadzie memcpy i kilka poprawek. – Gleno

+0

Jak w pomocy dla MemberwiseClone jest wspomniane, że robi płytką kopię może to być to samo, co przy użyciu BinaryFormatter? Wątpię, ponieważ ta funkcja nazywa się DeepCopy, co do której nazwa jest niepoprawna. Chodzi mi o to, że głęboki klon powinien również klonować obiekty z odniesieniami ??? – 000

0

Korzystanie z serializacji to bardzo sprytny pomysł. Jednak ustawianie właściwości jeden po drugim powinno być równie szybkie i przez większość czasu (z wyjątkiem sytuacji, gdy obiekty są naprawdę małe) musi działać szybciej - wykonujesz mniej pracy, a nie używasz odbicia.

Czy można odtworzyć przypadek, w którym użycie serializacji było szybsze niż kopiowanie właściwości "ręcznie"?

0

Serializacja jest zdecydowanie wolniejsza niż po prostu przypisanie nieruchomości.

Serializacja binarna jest preferowana, ponieważ kod jest łatwy i łatwy w utrzymaniu.

Przypisanie własności jest lepsze, ponieważ może być podczas klonowania, nie chcesz sklonować właściwości obiektu, ale chcesz je serializować przy okazji. Krótko mówiąc masz większą kontrolę nad przepływem.

2

W mojej poprzedniej roli zbadaliśmy ten problem, ponieważ buforowaliśmy obiekty i chcieliśmy je sklonować przed przekazaniem ich z pamięci podręcznej.

Zrobiliśmy kilka szczegółowych badań porównawczych i okazało się, że ustawienie właściwości zawsze co najmniej o rząd wielkości szybciej ówczesnego podejścia BinaryFormatter, choć oczywiście wymaga wdrożenia ręcznie walcowane w przeciwieństwie do znacznie prostszej BinaryFormatter podejścia. W przypadku wykresów głębokich obiektów różnica stała się bardziej wyraźna IIRC.

W końcu zdecydowaliśmy się na trzech płaszczyznach podejście do „klonowania”:

  • jeśli typ była niezmienna (co chcemy wyrazić z interfejsem markera, IImutable, ale równie dobrze może użyć atrybutu lub somesuch), "klonowalibyśmy", zwracając oryginalną instancję. Ponieważ wiedzieliśmy, że nikt nie może go zmutować, można bezpiecznie powrócić do tej samej instancji. Oczywiście był to najszybszy typ "klona", chociaż w rzeczywistości nie był wcale klonem.
  • Jeśli typ zaimplementował nasz własny interfejs IDeepCloneable<T> (który byłby twoim drugim przykładem - ale generycznym), skorzystalibyśmy z tego. [Ten ogólny interfejs odziedziczy po nietypowym odpowiedniku.

Wspominam podejście „niezmienny”, ponieważ w zależności od tego, co robisz, czasami najlepszym sposobem jest przeprojektowanie klas musisz sklonować więc nie muszą być sklonowana w ogóle. Jeśli są one w zasadzie tylko do odczytu, raz utworzone, jest to łatwe; ale nawet jeśli nie, czasami przydatne jest podejście typu builder/immutable (zob. Uri vs. UriBuilder w środowisku zrębowym. Ten pierwszy jest w zasadzie niezmienny, podczas gdy drugi może być użyty do zbudowania i/lub zmutowania przykładów tego pierwszego).

0

Wszystkie podejścia zakładają: 1) serializację i 2) proste (wystarczające) obiekty. Jeśli masz obiekty, które mogą zawierać hashtables, które mogą zawierać hashtables, bez wymuszonych ograniczeń głębi, to masz bałagan na rękach bez indeksowania rekursywnych obiektów.

Powiązane problemy