Что я должен делать при работе с массивами?

Я пытаюсь написать DynamicMethod для переноса кода cpblk IL. Мне нужно скопировать куски байт-массивов и на платформы x64, это, по-видимому, самый быстрый способ сделать это. Array.Copy и Buffer.BlockCopy работают, но я хотел бы изучить все варианты.

Моя цель - скопировать управляемую память из одного байтового массива в новый массив управляемых байтов. Моя проблема заключается в том, как я знаю, как правильно "привязывать" память. Я не хочу, чтобы сборщик мусора перемещал массивы и разбивал все. SO далеко он работает, но я не уверен, как проверить, является ли это безопасным GC.

// copying 'count' bytes from offset 'index' in 'source' to offset 0 in 'target'
// i.e. void _copy(byte[] source, int index, int count, byte[] target)

static Action<byte[], int, int, byte[]> Init()
{
    var dmethod = new DynamicMethod("copy", typeof(void), new[] { typeof(object),typeof(byte[]), typeof(int), typeof(int),typeof(byte[]) },typeof(object), true);
    var il = dmethod.GetILGenerator();

    il.DeclareLocal(typeof(byte).MakeByRefType(), true);
    il.DeclareLocal(typeof(byte).MakeByRefType(), true);
    // pin the source
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Ldarg_2);
    il.Emit(OpCodes.Ldelema, typeof(byte));
    il.Emit(OpCodes.Stloc_0);
    // pin the target
    il.Emit(OpCodes.Ldarg_S,(byte)4);
    il.Emit(OpCodes.Ldc_I4_0);
    il.Emit(OpCodes.Ldelema, typeof(byte));
    il.Emit(OpCodes.Stloc_1);

    il.Emit(OpCodes.Ldloc_1);
    il.Emit(OpCodes.Ldloc_0);
    // load the length
    il.Emit(OpCodes.Ldarg_3);
    // perform the memcpy
    il.Emit(OpCodes.Unaligned,(byte)1);
    il.Emit(OpCodes.Cpblk);

    il.Emit(OpCodes.Ret);
    return dmethod.CreateDelegate(typeof(Action<byte[], int, int, byte[]>)) as Action<byte[], int, int, byte[]>;
}

Ответ 1

Я считаю, что ваше использование закрепленных локальных переменных правильное.

Ответ 2

Вам не нужно ничего связывать в этом методе, если вы хотите вывести его, а затем привязать свой массив до ввода этого метода. Вам не нужно указывать какой-либо указатель, потому что адрес элемента всегда так же, если вы не перезапустите свою программу, вы можете даже запаковать его в тип intptr без каких-либо проблем.

.maxstack 3
ldarg.0
ldarg.1
ldelema int8

ldarg.2
ldarg.3
ldelema int8

ldarg.s 4
cpblk

ret

Ответ 3

void cpblk <T> (ref T src, ref T dst, int c_elem)

Копирует элементы c_elem типа T из src в dst с помощью инструкции IL cpblk. Обратите внимание, что c_elem указывает количество элементов, а не количество байтов. Протестировано с помощью С# 7 и .NET 4.7. См. Пример использования ниже.

public static class IL<T>
{
    public delegate void _cpblk_del(ref T src, ref T dst, int c_elem);
    public static readonly _cpblk_del cpblk;

    static IL()
    {
        var dm = new DynamicMethod("cpblk+" + typeof(T).FullName,
            typeof(void),
            new[] { typeof(T).MakeByRefType(), typeof(T).MakeByRefType(), typeof(int) },
            typeof(T),
            true);

        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_2);

        int cb = Marshal.SizeOf<T>();
        if (cb > 1)
        {
            il.Emit(OpCodes.Ldc_I4, cb);
            il.Emit(OpCodes.Mul);
        }

        byte align;
        if ((cb & (align = 1)) != 0 ||
            (cb & (align = 2)) != 0 ||
            (cb & (align = 4)) != 0)
            il.Emit(OpCodes.Unaligned, align);

        il.Emit(OpCodes.Cpblk);
        il.Emit(OpCodes.Ret);
        cpblk = (_cpblk_del)dm.CreateDelegate(typeof(_cpblk_del));
    }
}

Обратите внимание, что этот код предполагает, что элементы байт-пакеты (т.е. не заполняются между отдельными элементами) и выровнены в соответствии с их размером. В частности, адреса источника и получателя должны быть делятся на 1 << floor(log₂(sizeof(T) & 0xF)) иначе, если sizeof(T) % 8 отличен от нуля, тогда префикс OpCodes.Unaligned испускается, указывая старший делитель этого остатка среди { 1, 2 или 4}. Для выравнивания 8 -byte префикс не требуется.

В качестве примера, для 11-байтовой структуры требуется префикс выравнивания 1, потому что даже если первый элемент в диапазоне имеет четырехугольник, байт-упаковка означает, что смежные не будут, Обычно CLR организует массивы таким образом, и вам не нужно беспокоиться об этих проблемах.

Применение:

var src = new[] { 1, 2, 3, 4, 5, 6 };
var dst = new int[6];

IL<int>.cpblk(ref src[2], ref dst[3], 2);      // dst => { 0, 0, 0, 3, 4, 0 }

Автоматический вывод типа (необязательно):

Для автоматического вывода типа вы можете также включить следующий класс:

public static class IL
{
    public static void cpblk<T>(ref T src, ref T dst, int c_elem) 
        => IL<T>.cpblk(ref src, ref dst, c_elem);
}

При этом вам не нужно указывать аргументы типа, а предыдущий пример просто:

IL.cpblk(ref src[2], ref dst[3], 2);