在处理数组时我应该固定什么?

时间:2014-06-09 14:56:27

标签: clr cil reflection.emit

我尝试编写DynamicMethod来封装cpblk IL操作码。我需要复制字节数组块并在x64平台上,这应该是最快的方法。 Array.CopyBuffer.BlockCopy都有效,但我想探索所有选项。

我的目标是将托管内存从一个字节数组复制到一个新的托管字节数组。我担心的是我怎么知道如何正确地#34; pin"记忆位置。我不希望垃圾收集器移动数组并破坏所有内容。到目前为止它是有效的,但我不确定如何测试这是否是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[]>;
}

3 个答案:

答案 0 :(得分:2)

我相信您对固定局部变量的使用是正确的。

答案 1 :(得分:1)

如果要在输入此方法之前固定然后固定数组,则不需要在此方法中固定任何内容。您不需要固定任何指针,因为除非您重新启动程序,否则元素的地址总是相同,您甚至可以毫无问题地将其存入intptr类型。

.maxstack 3
ldarg.0
ldarg.1
ldelema int8

ldarg.2
ldarg.3
ldelema int8

ldarg.s 4
cpblk

ret

答案 2 :(得分:0)

  

无效 cpblk &lt; T&gt;(ref T src ,ref T dst ,int c_elem )< / p>

使用 cpblk IL指令将 src 中的 c_elem 元素复制到 dst 。请注意, c_elem 表示元素的数量,而不是字节数。使用 C#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}}中的余数的最高除数。 strong> 1 , 2 4 }。对于 8 字节对齐,不需要前缀。

例如,一个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);