如何摆脱边界检查

9 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]
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

问题

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

笔记

  • 我尝试进行 if 检查,希望编译器能够得到它,但这使情况变得更糟。
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
Run Code Online (Sandbox Code Playgroud)

这会生成:

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
Run Code Online (Sandbox Code Playgroud)

如你所见,这没有帮助。

  • 我能做的是:使用unsafe
public static int G(int[] M, int i) 
{
    if (i >= 0 && i < M.Length)
        return M[i];

    return -1;
}
Run Code Online (Sandbox Code Playgroud)

这会生成我正在寻找的内容:

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

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

JL0*_*0PD 8

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

\n

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

\n
[MethodImpl(MethodImplOptions.AggressiveInlining)]\nstatic T FastAccessValue<T>(T[] ar, int index)\n{\n       ref T tableRef = ref MemoryMarshal.GetArrayDataReference(ar);\n       return Unsafe.Add(ref tableRef, (nint)index);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

哪个是安全(?)相当于不安全版本

\n
[MethodImpl(MethodImplOptions.AggressiveInlining)]\nstatic unsafe T FastAccessValueUnsafe<T>(T[] ar, int index) where T : unmanaged\n{\n     fixed(T* ptr = ar)\n     {\n         return ptr[index];\n     }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

不限于unmanaged结构。

\n

通过不安全的访问,它甚至可以在大数据(超过百万个项目)上执行速度提高 10%

\n
public int SumUnsafe(int[] ints, int length)\n{\n    int sum = 0;\n    for (int i = 0; i < length; i++)\n    {\n        sum += FastAccessValue(ints, i);\n    }\n    return sum;\n}\n
Run Code Online (Sandbox Code Playgroud)\n
public int SumDirect(int[] ints, int length)\n{\n    int sum = 0;\n    for (int i = 0; i < ints.Length; i++)\n    {\n        sum += ints[i];\n    }\n    return sum;\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
方法整数长度意思是错误标准差代码大小
直接求和Int32[100000]10000080.13 \xce\xbcs0.748 \xce\xbcs0.700 \xce\xbcs29 乙
总和不安全Int32[100000]10000081.99 \xce\xbcs0.535 \xce\xbcs0.446 \xce\xbcs33乙
直接求和Int32[1000000]1000000854.73\xce\xbcs5.216 \xce\xbcs第4.624章29 乙
总和不安全Int32[1000000]1000000795.10\xce\xbcs2.680 \xce\xbcs第2.238章33乙
直接求和Int32[10000000]1000000010,104.72\xce\xbcs27.199 \xce\xbcs22.712\xce\xbcs29 乙
总和不安全Int32[10000000]100000009,126.06 \xce\xbcs30.329 \xce\xbcs26.886 \xce\xbcs33乙
\n
\n

基准位于这个要点

\n