通过Span <T>修改变量时,优化的构建和JIT编译是否会产生问题?

ant*_*rds 11 c# memory compilation

假设我MemoryMarshal.CreateSpan用来访问本地值类型的字节,例如以下(不是很有用)的代码:

using System;
using System.Runtime.InteropServices;

// namespace and class boilerplate go here

private static void Main()
{
    int value = 0;
    Span<byte> valueBytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1));

    var random = new Random();
    while (value >= 0) // the check in question
    {
        random.NextBytes(valueBytes);
        Console.WriteLine(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

尽管此代码按预期工作,但是如果未在循环中修改true变量value(除非通过valueBytes跨度间接修改),则指示的检查是否可以保证在编译为IL和JIT编译时仍可生存,而没有优化为?我可以依靠读value给我写的内容的阅读valueBytes吗,还是容易重新排序?还是因为最近刚接触C ++而感到偏执?

(请注意,我知道还有其他方法可以达到上述代码的预期效果,这不是关于如何获取全范围的32位随机整数或关于某个较大应用程序的XY问题的问题,我我试图将这段代码放入,不存在更大的应用程序)

dym*_*oid 2

我认为,唯一明确的答案可以由 Roslyn 和 RyuJIT 方面实现编译器优化的人员提供。

既然你使用的是.NET Core,你当然可以深入研究源代码并自己找到答案。不过,这将是特定编译器版本的答案。

查看为您的代码片段生成的 IL 代码:

// int value = 0;
ldc.i4.0
stloc.0

// MemoryMarshal.CreateSpan(ref value, 1)
ldloca.s 0
ldc.i4.1
call valuetype System.Span`1<!!0> System.Runtime.InteropServices.MemoryMarshal::CreateSpan<int32>(!!0&, int32)

// the rest is omitted
Run Code Online (Sandbox Code Playgroud)

注意ldloca.s操作码。此操作将局部变量的地址加载到计算堆栈上

虽然我无法向您提供证明这一点的官方链接,但我很确定 C# 和 JIT 编译器都不会优化该局部变量 - 只是因为它的地址已被使用,所以这个局部变量有可能会通过以下方式发生变异:它的地址。

如果您查看生成的汇编代码,您将看到这一点:局部变量在那里并且被放置到堆栈中,它不是仅寄存器变量。

// int value = 0;
xor         ecx,ecx  
mov         dword ptr [rsp+3Ch],ecx 

WHILE_LOOP_START:
// ... do stuff

// effectively: if (value >= 0) goto WHILE_LOOP_START
cmp         dword ptr [rsp+3Ch],0  
jge         WHILE_LOOP_START  
Run Code Online (Sandbox Code Playgroud)

尝试编写一些不产生ldloca.s操作码的代码(例如仅++value在循环中),该value变量很可能会成为仅寄存器变量。

如果您以从未写入的方式修改代码value(初始化除外),那么 JIT 编译器实际上将完全消除检查和变量本身:

LOOP:

// Console.WriteLine(0)
xor         ecx,ecx  
call        CONSOLE_WRITE_LINE

// while (true)
jmp         LOOP
Run Code Online (Sandbox Code Playgroud)

但有趣的是,C# 编译器不会进行这种优化:

// int value = 0;
ldc.i4.0
stloc.0

br.s WHILE_CHECK

LOOP_START:
// Console.WriteLine(value)
ldloc.0
call void System.Console::WriteLine(int32)

WHILE_CHECK:
// effectively: if (value >= 0) goto LOOP_START
ldloc.0
ldc.i4.0
bge.s LOOP_START
Run Code Online (Sandbox Code Playgroud)

同样,我的答案中的 IL 和汇编代码是特定于平台和编译器的(甚至是特定于 CLR 的)。我无法向您提供证明文件。但我很确定没有编译器会优化获取其地址的局部变量,甚至在调用方法/函数时将其用作参数。

也许 Roslyn 和 RyuJIT 团队的人可以给你更好的答案。