2

ここには MemberwiseClone に関するかなりの数の質問がありますが、これを正確にカバーするものは見つかりません。

私が理解しているように、MemberwiseClone は基本的にオブジェクトのメモリ領域をコピーし、それを別の場所にダンプして、そのオブジェクトの新しいインスタンスと呼びます。明らかに非常に高速で、大きなオブジェクトの場合、コピーを作成する最も速い方法です。単純なコンストラクターを持つ小さなオブジェクトの場合、インスタンスを作成して個々のプロパティをコピーする方が迅速です。

現在、MemberwiseClone を使用しているタイトなループがあります。MemberwiseClone は常に新しいインスタンスを作成するため、大量のメモリ割り当てと再割り当てが発生し、パフォーマンスが低下します。すべてのプロパティを既存のオブジェクトに個別にコピーする非常に大きなオブジェクトのカスタム コピーを作成しました。これは、MemberwiseClone を使用するよりも全体的にわずかに高速です。

メモリのチャンク全体を取得して既存のオブジェクトにダンプできれば、GC が何も心配する必要がないため、パフォーマンスが大幅に向上すると思います。うまくいけば、私もこのメッセージを取り除くでしょう:

明確にするために、ある既存のオブジェクトから別のオブジェクトにできるだけ早くプロパティをコピーしたいと考えています。必要なのは浅いコピーだけです。

メッセージ 2 DA0023: (平均)% GC 時間 = 19.30; % GC の時間は比較的長いです。ガベージ コレクションのオーバーヘッドが過剰であることを示すこの兆候は、アプリケーションの応答性に影響を与える可能性があります。.NET メモリ割り当てデータとオブジェクトの有効期間情報を収集して、アプリケーションが使用するメモリ割り当てのパターンをよりよく理解できます。

コードの安全性に制限はありません。ここでのゲームの目的は速度です。

これが本当に必要かどうかを尋ねたり、時期尚早の最適化について話したりしないでください。

ご協力いただきありがとうございます。

答え

以下のアドバイスに従って、オブジェクト内に構造体を埋め込み、その中にすべてのプロパティを格納します。次に、その構造体のコピーを取得して、1 つの割り当てですべてのプロパティをコピーできます。これにより、フィールドごとにコピーする場合と比較して 50% 以上の速度が向上し、MemberwiseClone を使用して毎回新しいオブジェクトを作成する場合よりも約 60% 速度が向上しました。

4

2 に答える 2

2

一般に、フィールドごとにコピーするよりも高速になることはありません。

これは一般的に。1 つの例外があります。クラスが blittable (blittable でないクラスへの参照を含まない) であり、かつ で宣言されている場合 [StructLayout(LayoutKind.Sequential)](または で宣言されて[StructLayout(LayoutKind.Explicit)]いますが、テストはより複雑です)、固定することができます。

GCHandle handle = GCHandle.Alloc(refToYourClass, GCHandleType.Pinned);

unsafeそこから新しい世界が開かれます...安全でない(キーボードとしても「ハサミで実行する」としても安全でない)コードを使用し、クラスのコンテンツをバイト単位で直接コピーできます。

しかし、これが機能するには、クラスが blittable でなければなりません!

最も速い方法はUnsafeCopy8、8 バイトのブロック (32 ビットと 64 ビットの両方) をコピーすることです。最速の PInvoke メソッドは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);
        }
    }
}
于 2013-08-06T17:48:16.293 に答える