Самый быстрый способ сделать мелкое копирование в C #

Интересно, какой самый быстрый способ сделать поверхностное копирование в C #? Я только знаю, что есть 2 способа сделать поверхностное копирование:

MemberwiseCloneСкопируйте каждое поле одно за другим (вручную)

Я обнаружил, что (2) быстрее, чем (1). Мне интересно, есть ли другой способ сделать поверхностное копирование?

 Daniel Brückner08 июн. 2009 г., 21:18
Это мелкая копия, если копирование не выполняется рекурсивно.
 tep08 июн. 2009 г., 21:18
Я хочу сделать мелкую копию на самом деле
 Luaan19 февр. 2014 г., 11:21
Если вам нужно много мелких копий, возможно, будет лучше использовать struct вместо class?
 Jason Williams08 июн. 2009 г., 21:23
Мелкая копия - это когда вы дублируете ссылки на данные (поэтому обе копии ссылаются на общую версию данных). Глубокая копия - это когда вы дублируете все фактические данные (так что есть две независимые копии всего).

Ответы на вопрос(5)

о где-то в Интернете:

public static class Cloner
{
    static Dictionary<Type, Delegate> _cachedIL = new Dictionary<Type, Delegate>();

    public static T Clone<T>(T myObject)
    {
        Delegate myExec = null;

        if (!_cachedIL.TryGetValue(typeof(T), out myExec))
        {
            var dymMethod = new DynamicMethod("DoClone", typeof(T), new Type[] { typeof(T) }, true);
            var cInfo = myObject.GetType().GetConstructor(new Type[] { });

            var generator = dymMethod.GetILGenerator();

            var lbf = generator.DeclareLocal(typeof(T));

            generator.Emit(OpCodes.Newobj, cInfo);
            generator.Emit(OpCodes.Stloc_0);

            foreach (var field in myObject.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            {
                // Load the new object on the eval stack... (currently 1 item on eval stack)
                generator.Emit(OpCodes.Ldloc_0);
                // Load initial object (parameter)          (currently 2 items on eval stack)
                generator.Emit(OpCodes.Ldarg_0);
                // Replace value by field value             (still currently 2 items on eval stack)
                generator.Emit(OpCodes.Ldfld, field);
                // Store the value of the top on the eval stack into the object underneath that value on the value stack.
                //  (0 items on eval stack)
                generator.Emit(OpCodes.Stfld, field);
            }

            // Load new constructed obj on eval stack -> 1 item on stack
            generator.Emit(OpCodes.Ldloc_0);
            // Return constructed object.   --> 0 items on stack
            generator.Emit(OpCodes.Ret);

            myExec = dymMethod.CreateDelegate(typeof(Func<T, T>));

            _cachedIL.Add(typeof(T), myExec);
        }

        return ((Func<T, T>)myExec)(myObject);
    }
}
 Noldorin08 июн. 2009 г., 21:21
Поскольку для этого используется отражение, оно вряд ли будет быстрее, чем object.MemberwiseClone (который выполняется внутри CLR). Я рекомендую OP просто использовать MemberwiseClone в большинстве ситуаций или копировать поля вручную для каждого класса, если требуется высокая производительность.
 tep08 июн. 2009 г., 21:24
Кажется, вручную скопировать поле это самый быстрый способ?

На самом деле MemberwiseClone обычно намного лучше других, особенно для сложного типа.

а также

Я не совсем понимаю. MemberwiseClone () должен уничтожить производительность чего-либо еще для мелкой копии. [...]

Теоретически лучшая реализация поверхностной копии - это конструктор копирования C ++:знает размер компиляции, а затем делает для каждого элемента клон всех полей. Следующая лучшая вещь используетmemcpy или что-то подобное, что в основном какMemberwiseClone должно сработать. Это означает, что в теории это должно уничтожить все другие возможности с точки зрения производительности.Правильно?

... но, по-видимому, это не так быстро, и не стирает все другие решения. Внизу я опубликовал решение, которое в 2 раза быстрее. Так:Неправильно.

Тестирование внутренних элементов MemberwiseClone

Давайте начнем с небольшого теста с использованием простого типа blittable, чтобы проверить основные предположения о производительности:

[StructLayout(LayoutKind.Sequential)]
public class ShallowCloneTest
{
    public int Foo;
    public long Bar;

    public ShallowCloneTest Clone()
    {
        return (ShallowCloneTest)base.MemberwiseClone();
    }
}

Тест разработан таким образом, чтобы мы могли проверить производительностьMemberwiseClone agaist rawmemcpy, что возможно, потому что это тип blittable.

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

Реализация 1:

ShallowCloneTest t1 = new ShallowCloneTest() { Bar = 1, Foo = 2 };
Stopwatch sw = Stopwatch.StartNew();
int total = 0;
for (int i = 0; i < 10000000; ++i)
{
    var cloned = t1.Clone();                                    // 0.40s
    total += cloned.Foo;
}

Console.WriteLine("Took {0:0.00}s", sw.Elapsed.TotalSeconds);

Обычно я запускал эти тесты несколько раз, проверял выходные данные сборки, чтобы убедиться, что они не были оптимизированы, и т. Д. Конечный результат состоит в том, что я знаю приблизительно, сколько секунд стоит эта строка кода, что составляет 0,40 с. мой компьютер. Это наша базовая линия, использующаяMemberwiseClone.

Реализация 2:

sw = Stopwatch.StartNew();

total = 0;
uint bytes = (uint)Marshal.SizeOf(t1.GetType());
GCHandle handle1 = GCHandle.Alloc(t1, GCHandleType.Pinned);
IntPtr ptr1 = handle1.AddrOfPinnedObject();

for (int i = 0; i < 10000000; ++i)
{
    ShallowCloneTest t2 = new ShallowCloneTest();               // 0.03s
    GCHandle handle2 = GCHandle.Alloc(t2, GCHandleType.Pinned); // 0.75s (+ 'Free' call)
    IntPtr ptr2 = handle2.AddrOfPinnedObject();                 // 0.06s
    memcpy(ptr2, ptr1, new UIntPtr(bytes));                     // 0.17s
    handle2.Free();

    total += t2.Foo;
}

handle1.Free();
Console.WriteLine("Took {0:0.00}s", sw.Elapsed.TotalSeconds);

Если вы внимательно посмотрите на эти цифры, вы заметите несколько вещей:

Создание объекта и его копирование займет примерно 0,20 с. При нормальных обстоятельствах это самый быстрый код, который вы можете иметь.Однако для этого вам нужно закрепить и открепить объект. Это займет у вас 0,81 секунды.

Так почему же все это так медленно?

Мое объяснение состоит в том, что это имеет отношение к GC. В основном реализации не могут полагаться на тот факт, что память останется неизменной до и после полного GC (адрес памяти может быть изменен во время GC, что может произойти в любой момент, в том числе во время мелкой копии). Это означает, что у вас есть только 2 возможных варианта:

Закрепление данных и копирование. Обратите внимание, чтоGCHandle.Alloc это всего лишь один из способов сделать это, хорошо известно, что такие вещи, как C ++ / CLI, обеспечат вам лучшую производительность.Перечисление полей. Это гарантирует, что между сборками GC вам не нужно делать что-то необычное, а во время сборов GC вы можете использовать возможность GC для изменения адресов в стеке перемещенных объектов.

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

(Намного) более быстрая реализация

Во всех случаях наш неуправляемый код не может делать предположения о размере типов, и он должен закреплять данные. Создание предположений о размере позволяет компилятору оптимизировать работу, например, развертывание циклов, распределение регистров и т. Д. (Подобно тому, как C ++ копирует Ctor быстрее, чемmemcpy). Отсутствие необходимости прикреплять данные означает, что мы не получим дополнительного снижения производительности. Поскольку .NET JIT предназначен для ассемблера, теоретически это означает, что мы должны быть в состоянии сделать более быструю реализацию, используя простое излучение IL и позволяя компилятору оптимизировать его.

Итак, подведем итог, почему это может быть быстрее, чем собственная реализация?

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

То, к чему мы стремимся, это производительность сырьяmemcpy или лучше: 0,17 с.

Для этого мы не можем использовать больше, чем простоcall, создать объект и выполнить кучуcopy инструкции. Это выглядит какCloner реализация выше, но некоторые важные различия (наиболее значимые: нетDictionary и нет лишнихCreateDelegate звонки). Поехали:

public static class Cloner<T>
{
    private static Func<T, T> cloner = CreateCloner();

    private static Func<T, T> CreateCloner()
    {
        var cloneMethod = new DynamicMethod("CloneImplementation", typeof(T), new Type[] { typeof(T) }, true);
        var defaultCtor = typeof(T).GetConstructor(new Type[] { });

        var generator = cloneMethod .GetILGenerator();

        var loc1 = generator.DeclareLocal(typeof(T));

        generator.Emit(OpCodes.Newobj, defaultCtor);
        generator.Emit(OpCodes.Stloc, loc1);

        foreach (var field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
        {
            generator.Emit(OpCodes.Ldloc, loc1);
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldfld, field);
            generator.Emit(OpCodes.Stfld, field);
        }

        generator.Emit(OpCodes.Ldloc, loc1);
        generator.Emit(OpCodes.Ret);

        return ((Func<T, T>)cloneMethod.CreateDelegate(typeof(Func<T, T>)));
    }

    public static T Clone(T myObject)
    {
        return cloner(myObject);
    }
}

Я проверил этот код с результатом: 0,16 с. Это означает, что это примерно в 2,5 раза быстрее, чемMemberwiseClone.

Что еще более важно, эта скорость находится на одном уровне сmemcpyЭто более или менее «оптимальное решение в нормальных условиях».

Лично я считаю, что это самое быстрое решение - и лучшая его часть: если среда выполнения .NET станет быстрее (надлежащая поддержка инструкций SSE и т. Д.), То же самое будет и с этим решением.

 atlaste25 мая 2016 г., 18:55
@ChrisGillum Не совсем. Они просто фокусируются на производительности разработчиков и обеспечивают разумную производительность. Если вы привыкли программировать низкоуровневый C ++, как я, превзойти .NET-приложение в 10 раз - это не проблема ...
 Bruce Pierson21 авг. 2015 г., 09:16
Но так как это статический универсальный класс, this.GetType () ничего не дает - вы не можете использовать здесь вывод типов, не так ли?
 atlaste21 авг. 2015 г., 09:14
@BrucePierson Ну да, это не так, как это задумано, верно? Тем не менее, если это ваш сценарий, он довольно прост в использованииthis.GetType() конечно. Лично я стараюсь не загромождать базовые классы такими функциями.
 Bruce Pierson21 авг. 2015 г., 09:07
Хм. Вы проверяли это призвание?Cloner<BaseClass>.Clone(someDerivedObject);? Я догадываюсь, что он потерпит неудачу, потому что вы используете статическую информацию поля времени компиляции для создания клонера. Реализации клонов часто определяются в базовом классе (а-ляMemberwiseClone), который не может знать о будущих производных классах, но все равно должен копировать все поля вниз по иерархии.
 atlaste21 авг. 2015 г., 09:20
@BrucePierson Вы меня неправильно поняли. Вы можете изменить все вхожденияtypeof(T) вsomeObject.GetType() в коде и хранить данные в словаре (вам нужна блокировка). Это просто съест твою работу. В качестве альтернативы вы можете создать клонabstractсделайте переопределения клона и вызовите мой код, который намного быстрее, или вызовите его напрямую, а не помещайте его в базовый класс (что я бы сделал) - этот поток в конце концов был связан с производительностью.
Решение Вопроса

минусов для каждого. Есть замечательная статьяВот в нем изложены несколько различных способов сделать копию в C #. Обобщить:

Клонировать вручную
Утомительный, но высокий уровень контроля.

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

Клон с отражением
По умолчанию мелкая копия, может быть переписана, чтобы сделать глубокую копию. Преимущество: автоматизировано. Недостаток: медленное отражение.

Клон с сериализацией
Легко, автоматизировано. Откажитесь от контроля, и сериализация будет самой медленной из всех.

Клон с ИЛ, клон с методами удлинения
Более продвинутые решения, не такие распространенные.

 tep08 июн. 2009 г., 21:33
Я вижу .. Из всех доступных вариантов, я думаю, (1) является самым быстрым. Спасибо!
 Luaan19 февр. 2014 г., 11:25
MemberwiseClone делает некоторые дополнительные проверки и тому подобное, поэтому для объектов с несколькими полями, это может быть немного медленнее. В любом случае, вы должны заботиться об этом, только если у вас действительно возникают проблемы с производительностью. Не оптимизируйте "на догадку", профиль. Если вы делаете так много клонирования, чтобы иметь проблемы сMemberwiseCloneвы, вероятно, должны использовать структуры, а не классы. Или, возможно, даже более агрессивные изменения в архитектуре необходимы: D
 Alkampfer31 авг. 2016 г., 12:41
Вы взглянули на библиотеку Fasterflect? У него есть хорошая возможность сделать глубокую сериализацию.
 Mr. TA22 апр. 2013 г., 17:51
@tepe nope, MemberwiseClone должен быть самым быстрым. Ваши результаты, вероятно, были вызваны режимом отладки / выпуска или каким-либо другим фактором такого рода.

Зачем все усложнять? MemberwiseClone будет достаточно.

public class ClassA : ICloneable
{
   public object Clone()
   {
      return this.MemberwiseClone();
   }
}

// let's say you want to copy the value (not reference) of the member of that class.
public class Main()
{
    ClassA myClassB = new ClassA();
    ClassA myClassC = new ClassA();
    myClassB = (ClassA) myClassC.Clone();
}
 Jugal Panchal15 июл. 2013 г., 13:15
Спасибо, это решает мою проблему.

MemberwiseClone() долженаннигилировать исполнение чего-либо еще для мелкой копии. В CLI любой тип, кроме RCW, должен иметь возможность мелкого копирования следующей последовательностью:

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

Может ли кто-нибудь из команды внутреннего персонала CLR объяснить, почему это не так?

 atlaste20 июл. 2015 г., 10:59
@SamHaswellMemberwiseClone являетсяexternal вызов, который означает, что это требует закрепления. Закрепление является относительно медленным процессом. Я разместил подробности моих тестов и результаты в моем ответе ниже. У меня больше нет данных о тесте исходных вызовов, но я уверен, что закрепление / собственное взаимодействие и дополнительные вызовы vtable (клон, sizeof) учитывают накладные расходы. Memcpy, что вы нашли.
 Nicholas Petersen27 авг. 2014 г., 22:33
@ ta.speot.is, ба-хамбуг. Это хороший вопрос, и большинство из нас участвуют в этих обсуждениях, чтобы лучше понять вещи в целом.
 ta.speot.is19 мая 2014 г., 06:12
-1 Не ответ.

Ваш ответ на вопрос