c# - 通过 Span<T> 修改变量时,优化构建和 JIT 编译会产生问题吗?

标签 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);
    }
}

虽然此代码按预期工作,但如果变量 value 为除了通过 valueBytes 跨度间接地在循环中修改吗?我可以依靠对 value 的读取来为我提供写入到 valueBytes 的内容,或者这是否容易被重新排序?还是我只是因为最近涉足了一点 C++ 而变得偏执?

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

最佳答案

我认为,唯一确定的答案可以由在 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

注意 ldloca.s 操作码。此操作loads the address of the local variable onto the evaluation stack .

虽然我无法为您提供官方链接来证明这一点,但我很确定 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  

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

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

LOOP:

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

// while (true)
jmp         LOOP

不过有趣的是,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

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

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

关于c# - 通过 Span<T> 修改变量时,优化构建和 JIT 编译会产生问题吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55068872/

相关文章:

android - ffmpeg 无法编译

linux - 使用默认配置选项编译 lighttpd

UNIX 域套接字与共享内存(映射文件)

c# - 在数组中的特定索引中搜索特定数字

c# - 转换到一个奇怪的重复派生类

c# - 如何为 TextBox 实现良好且高效的撤消/重做功能

ios - 在后台线程上使用CoreGraphics调整图像大小时发生内存泄漏

c - 大批。访问 0xFFFFFFFF 元素

Java:使用高版本 JDK 编译但在低版本 JRE 上运行时,防止使用高版本 API

c# - 如何使用从查询中提取的另一个对象的值设置 linq 查询中所有项目的属性?