问题
There are quite a few questions on here about MemberwiseClone, but I can't find anything covering this exactly.
As I understand it, MemberwiseClone basically just copies an area of memory for an object, dumps it somewhere else and calls it a new instance of that object. Obviously very quick, and for large objects it is the quickest way to make a copy. For small objects with simple constructors, it's quicker to create an instance and copy the individual properties.
Now, I have a tight loop in which I'm using MemberwiseClone. As MemberwiseClone always creates a new instance this leads to a lot of memory allocation, and reallocation which isn't great for performance. I've written a custom copy for a very large object that copies all the properties individually onto an existing object, which overall is marginally quicker than using MemberwiseClone,
I feel that if I could grab the whole chunk of memory and dump it onto the existing object, I'd achieve a decent performance boost as the GC wouldn't have to worry about anything. Hopefully I'll also get rid of this message:
Just to clarify, I want to copy the properties from one existing object to another as quickly as possible. A shallow copy is all I need.
Message 2 DA0023: (Average)% Time in GC = 19.30; % Time in GC is relatively high. This indication of excessive amount of garbage collection overhead could be impacting the responsiveness of your application. You can gather .NET memory allocation data and object lifetime information to understand the pattern of memory allocation your application uses better.
I have no restrictions on code safety, speed is the aim of the game here.
No comments asking if this is really necessary or talking about premature optimisation please.
Thanks for your help.
Answer
Taking the advise below, of embedding a struct within the object and storing all properties in that. I could then take a copy of that struct to copy all the properties in a single assignment. This yielded more than a 50% speed increase compared to copying field by field, and about a 60% speed increase over creating a new object every time with MemberwiseClone.
回答1:
If your class is not inheritable, you could store the entire state of your class in an exposed-field structure (expose fields, not properties!). This would require that you prefix all field names with the name of the structure, which would be ugly in the source code, but accessing fields in that structure will be just as fast as would be accessing fields that were directly placed in the enclosing class. Doing this, however, will gain you the ability to copy the structure from one object instance into another using a simple assignment statement.
回答2:
In general you can't be faster than doing a copy field by field.
This in general. There is a single exception: if your class is blittable (so doesn't contain any reference to non-blittable classes) AND it's declared with the [StructLayout(LayoutKind.Sequential)]
(or with the [StructLayout(LayoutKind.Explicit)]
, but it's more complex to test) then you can pin it
GCHandle handle = GCHandle.Alloc(refToYourClass, GCHandleType.Pinned);
from there a new world opens... You can directly copy byte-by-byte the content of your class with some unsafe (both as unsafe
the keyboard and unsafe as "running-with-scissors") code.
BUT for this to work, your class MUST be blittable!
The fastest method is UnsafeCopy8
that copies blocks of 8 bytes (both at 32bits and at 64bits). The fastest PInvoke method is memcpy
.
[StructLayout(LayoutKind.Sequential)]
class MyClass
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D { get; set; }
public int E { get; set; }
public int F { get; set; }
public int G { get; set; }
public byte H { get; set; }
}
class Program
{
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
static extern IntPtr memcpy(IntPtr dest, IntPtr src, UIntPtr count);
[DllImport("kernel32.dll", SetLastError = false)]
static extern void CopyMemory(IntPtr dest, IntPtr src, UIntPtr count);
static void Main()
{
MyClass mc = new MyClass { A = 1, B = 2, C = 3, D = 4, E = 5, F = 6, G = 7, H = 8 };
MyClass mc2 = new MyClass();
int size = Marshal.SizeOf(typeof(MyClass));
var gc = GCHandle.Alloc(mc, GCHandleType.Pinned);
var gc2 = GCHandle.Alloc(mc2, GCHandleType.Pinned);
IntPtr dest = gc2.AddrOfPinnedObject();
IntPtr src = gc.AddrOfPinnedObject();
// Pre-caching
memcpy(dest, src, (UIntPtr)size);
CopyMemory(dest, src, (UIntPtr)size);
UnsafeCopy(dest, src, size);
UnsafeCopy8(dest, src, size);
int cycles = 10000000;
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
var sw1 = Stopwatch.StartNew();
for (int i = 0; i < cycles; i++)
{
memcpy(dest, src, (UIntPtr)size);
}
sw1.Stop();
var sw2 = Stopwatch.StartNew();
for (int i = 0; i < cycles; i++)
{
CopyMemory(dest, src, (UIntPtr)size);
}
sw2.Stop();
var sw3 = Stopwatch.StartNew();
for (int i = 0; i < cycles; i++)
{
UnsafeCopy(dest, src, size);
}
sw3.Stop();
var sw4 = Stopwatch.StartNew();
for (int i = 0; i < cycles; i++)
{
UnsafeCopy8(dest, src, size);
}
sw4.Stop();
gc.Free();
gc2.Free();
Console.WriteLine("IntPtr.Size: {0}", IntPtr.Size);
Console.WriteLine("memcpy: {0}", sw1.ElapsedTicks);
Console.WriteLine("CopyMemory: {0}", sw2.ElapsedTicks);
Console.WriteLine("UnsafeCopy: {0}", sw3.ElapsedTicks);
Console.WriteLine("UnsafeCopy8: {0}", sw4.ElapsedTicks);
Console.ReadKey();
}
static unsafe void UnsafeCopy(IntPtr dest, IntPtr src, int size)
{
while (size >= sizeof(int))
{
*((int*)dest) = *((int*)src);
dest = (IntPtr)(((byte*)dest) + sizeof(int));
src = (IntPtr)(((byte*)src) + sizeof(int));
size -= sizeof(int);
}
if (size >= sizeof(short))
{
*((short*)dest) = *((short*)src);
dest = (IntPtr)(((byte*)dest) + sizeof(short));
src = (IntPtr)(((byte*)src) + sizeof(short));
size -= sizeof(short);
}
if (size == sizeof(byte))
{
*((byte*)dest) = *((byte*)src);
}
}
static unsafe void UnsafeCopy8(IntPtr dest, IntPtr src, int size)
{
while (size >= sizeof(long))
{
*((long*)dest) = *((long*)src);
dest = (IntPtr)(((byte*)dest) + sizeof(long));
src = (IntPtr)(((byte*)src) + sizeof(long));
size -= sizeof(long);
}
if (size >= sizeof(int))
{
*((int*)dest) = *((int*)src);
dest = (IntPtr)(((byte*)dest) + sizeof(int));
src = (IntPtr)(((byte*)src) + sizeof(int));
size -= sizeof(int);
}
if (size >= sizeof(short))
{
*((short*)dest) = *((short*)src);
dest = (IntPtr)(((byte*)dest) + sizeof(short));
src = (IntPtr)(((byte*)src) + sizeof(short));
size -= sizeof(short);
}
if (size == sizeof(byte))
{
*((byte*)dest) = *((byte*)src);
}
}
}
来源:https://stackoverflow.com/questions/18086950/memberwiseclone-equivalent-to-an-existing-object