如何摆脱边界检查

时间:2021-04-04 07:51:01

标签: c# arrays assembly x86-64

有没有办法去除 C# 中的数组边界检查?

这是我想要实现的目标:

public static int F(int[] M, int i) 
{
    return M[i]; // I can guarantee that [i] will never be outside of [0, M.Length]
}

在这个函数调用之前,我有一个已经检查边界的逻辑(其中包含一些额外的逻辑)。我要删除的内容如下:

Program.F(Int32[], Int32)
    L0000: sub rsp, 0x28
    L0004: cmp edx, [rcx+8]           ; I don't need this line
    L0007: jae short L0015            ; I don't need this line
    L0009: movsxd rax, edx
    L000c: mov eax, [rcx+rax*4+0x10]
    L0010: add rsp, 0x28
    L0014: ret
    L0015: call 0x00007ffc8877bc70    ; I don't need this line
    L001a: int3                       ; I don't need this line

问题

有没有办法去除这些指令?

注意

  • 我试图进行 if 检查,希望编译器能够得到它,但它使情况变得更糟。
public static int G(int[] M, int i) 
{
    if (i >= 0 && i < M.Length)
        return M[i];

    return -1;
}

这会产生:

Program.G(Int32[], Int32)
    L0000: sub rsp, 0x28
    L0004: test edx, edx
    L0006: jl short L001f
    L0008: mov eax, [rcx+8]
    L000b: cmp eax, edx
    L000d: jle short L001f
    L000f: cmp edx, eax
    L0011: jae short L0029
    L0013: movsxd rax, edx
    L0016: mov eax, [rcx+rax*4+0x10]
    L001a: add rsp, 0x28
    L001e: ret
    L001f: mov eax, 0xffffffff
    L0024: add rsp, 0x28
    L0028: ret
    L0029: call 0x00007ffc8877bc70
    L002e: int3

如您所见,它没有帮助。

  • 我能做的是:使用unsafe
public static unsafe int H(int* M, int i) 
{
    return M[i];
}

这产生了我想要的:

Program.H(Int32*, Int32)
    L0000: movsxd rax, edx
    L0003: mov eax, [rcx+rax*4]
    L0006: ret

但遗憾的是,我无法为我的项目启用 unsafe。 “非不安全”的世界有解决方案吗?

2 个答案:

答案 0 :(得分:1)

遗憾的是我无法添加评论,没有不安全的方法(据我所知) 您可能应该尝试解决不让您添加不安全的问题

答案 1 :(得分:0)

其实是有办法的。在 csFastFloat 存储库中偶然发现它。

这里的想法是使用 MemoryMarshall.GetArrayDataReference 获取对数组中第一项的引用,然后添加移位以获取实际值:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static T FastAccessValue<T>(T[] ar, int index)
{
       ref T tableRef = ref MemoryMarshal.GetArrayDataReference(ar);
       return Unsafe.Add(ref tableRef, (nint)index);
}

哪个是安全的(?) 等价于不安全的版本

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static unsafe T FastAccessValueUnsafe<T>(T[] ar, int index) where T : unmanaged
{
     fixed(T* ptr = ar)
     {
         return ptr[index];
     }
}

不限于仅 unmanaged 结构。

在不安全访问的情况下,它在处理大数据(超过百万项)时的执行速度甚至提高了 10%

public int SumUnsafe(int[] ints, int length)
{
    int sum = 0;
    for (int i = 0; i < length; i++)
    {
        sum += FastAccessValue(ints, i);
    }
    return sum;
}
public int SumDirect(int[] ints, int length)
{
    int sum = 0;
    for (int i = 0; i < ints.Length; i++)
    {
        sum += ints[i];
    }
    return sum;
}
<头>
方法 整数 长度 平均 错误 StdDev 代码大小
SumDirect Int32[100000] 100000 80.13 μs 0.748 μs 0.700 μs 29 B
求和不安全 Int32[100000] 100000 81.99 μs 0.535 μs 0.446 μs 33 B
SumDirect Int32[1000000] 1000000 854.73 μs 5.216 μs 4.624 μs 29 B
求和不安全 Int32[1000000] 1000000 795.10 μs 2.680 μs 2.238 μs 33 B
SumDirect Int32[10000000] 10000000 10,104.72 μs 27.199 μs 22.712 μs 29 B
求和不安全 Int32[10000000] 10000000 9,126.06 μs 30.329 μs 26.886 μs 33 B

基准位于此 gist