gpt4 book ai didi

c# - 我不了解 volatile 和 Memory-Barrier 的是

转载 作者:太空狗 更新时间:2023-10-30 00:10:35 25 4
gpt4 key购买 nike

循环提升 volatile 读取

我读过很多地方都说不能从循环或 if 中提升 volatile 变量,但是我在 C# 规范中找不到提到的任何地方。这是隐藏功能吗?

所有写入在 C# 中都是 volatile 的

这是否意味着所有写入都具有与 volatile 关键字相同的属性?例如,C# 中的普通写入具有发布语义?并且所有写入都会刷新处理器的存储缓冲区?

发布语义

这是一种正式的说法,即在完成 volatile 写入时处理器的存储缓冲区被清空吗?

获取语​​义

这是一种正式的说法,即不应将变量加载到寄存器中,而是每次都从内存中获取它?

this article , Igoro 谈到“线程缓存”。我完全理解这是虚构的,但他实际上指的是:

  • 处理器存储缓冲区
  • 将变量加载到寄存器中而不是每次都从内存中获取
  • 某种处理器缓存(是 L1 和 L2 等)

  • 还是这只是我的想象?

    延迟写入

    我读过很多地方写可以延迟。这是因为重新排序和存储缓冲区吗?

    内存屏障

    我知道副作用是在 JIT 将 IL 转换为 asm 时调用“lock or”,这就是为什么 Memory.Barrier 可以解决在 fx 这个例子中延迟写入主内存(在 while 循环中)的原因:
    static void Main()
    {
    bool complete = false;
    var t = new Thread (() =>
    {
    bool toggle = false;
    while (!complete) toggle = !toggle;
    });
    t.Start();
    Thread.Sleep (1000);
    complete = true;
    t.Join(); // Blocks indefinitely
    }

    但情况总是这样吗?对 Memory.Barrier 的调用是否总是将存储缓冲区获取更新的值刷新到处理器缓存中?我知道完整变量没有被提升到寄存器中,而是每次从处理器缓存中获取,但是由于调用 Memory.Barrier,处理器缓存会更新。

    我在这里如履薄冰,还是我对 volatile 和 Memory.Barrier 有某种了解?

    最佳答案

    那一口。。

    我将从您的一些问题开始,并更新我的答案。

    循环提升 volatile

    I have read many places that a volatile variable can not be hoisted from a loop or if, but I cannot find this mentioned any places in the C# spec. Is this a hidden feature?



    MSDN说“声明为 volatile 的字段不受假定由单个线程访问的编译器优化的影响”。这是一个广泛的声明,但它包括从循环中提升或“提升”变量。

    所有写入在 C# 中都是 volatile 的

    Does this mean that all writes have the same properties without, as with the volatile keyword? Eg ordinary writes in C# has release semantics? and all writes flushes the store buffer of the processor?



    常规写入不是 volatile 的。它们确实具有释放语义,但它们不会刷新 CPU 的写入缓冲区。至少,不是根据规范。

    来自乔达菲的 CLR 2.0 Memory Model

    Rule 2: All stores have release semantics, i.e. no load or store may move after one.



    我读过几篇文章,指出 C# 中的所有写入都是易变的(就像您链接的那个),但这是一个常见的误解。从马口( The C# Memory Model in Theory and Practice, Part 2 ):

    Consequently, the author might say something like, “In the .NET 2.0 memory model, all writes are volatile—even those to non-volatile fields.” (...) This behavior isn’t guaranteed by the ECMA C# spec, and, consequently, might not hold in future versions of the .NET Framework and on future architectures (and, in fact, does not hold in the .NET Framework 4.5 on ARM).



    发布语义

    Is this a formal way of saying that the store buffer of a processor is emptied when a volatile write is done?



    不,那是两种不同的东西。如果指令具有“释放语义”,则不会将存储/加载指令移动到所述指令下方。该定义没有说明刷新写入缓冲区。它只涉及指令重新排序。

    延迟写入

    I have read many places that writes can be delayed. Is this because of the reordering, and the store buffer?



    是的。编译器、抖动或 CPU 本身都可以延迟/重新排序写入指令。

    So a volatile write has two properties: release semantics, and store buffer flushing.



    有点。我更喜欢这样想:

    volatile 关键字的 C# 规范保证 属性:读取具有获取语义,写入具有释放语义。这是通过发出必要的释放/获取栅栏来完成的。

    实际的 Microsoft 的 C# 实现添加了另一个属性:读取将是新鲜的,写入将立即刷新到内存中并使其对其他处理器可见。为此,编译器发出 OpCodes.Volatile ,并且抖动接收到它并告诉处理器 不是 将此变量存储在其寄存器中。

    这意味着不保证即时性的不同 C# 实现将是一个完全有效的实现。

    内存屏障
    bool complete = false; 
    var t = new Thread (() =>
    {
    bool toggle = false;
    while (!complete) toggle = !toggle;
    });
    t.Start();
    Thread.Sleep(1000);
    complete = true;
    t.Join(); // blocks

    But is this always the case? Will a call to Memory.Barrier always flush the store buffer fetch updated values into the processor cache?



    这是一个提示:尝试将自己从诸如刷新存储缓冲区或直接从内存中读取之类的概念中抽象出来。内存屏障(或全栅栏)的概念与前两个概念没有任何关系。

    内存屏障只有一个目的:确保栅栏下方的存储/加载指令不会移动到栅栏上方,反之亦然。如果 C# 的 Thread.MemoryBarrier碰巧刷新挂起的写入,您应该将其视为副作用,而不是主要意图。

    现在,让我们进入正题。您发布的代码(在 Release模式下编译并在没有调试器的情况下运行时会阻塞)可以通过在 while 内的任何地方引入完整的栅栏来解决。堵塞。为什么?让我们首先展开循环。以下是前几次迭代的样子:
    if(complete) return;
    toggle = !toggle;

    if(complete) return;
    toggle = !toggle;

    if(complete) return;
    toggle = !toggle;
    ...

    因为 complete未标记为 volatile并且没有栅栏,允许编译器和 cpu 移动 complete 的读操作。 field 。
    事实上, CLR's Memory Model (see rule 6)允许在合并相邻负载时删除 (!) 负载。所以,这可能发生:
    if(complete) return;
    toggle = !toggle;
    toggle = !toggle;
    toggle = !toggle;
    ...

    请注意,这在逻辑上等同于将读取提升到循环之外,而这正是编译器可能会执行的操作。

    通过在 toggle = !toggle 之前或之后引入全围栏,你会阻止编译器向上移动读取并将它们合并在一起。
    if(complete) return;
    toggle = !toggle;
    #FENCE
    if(complete) return;
    toggle = !toggle;
    #FENCE
    if(complete) return;
    toggle = !toggle;
    #FENCE
    ...

    总之,解决这些问题的关键是确保指令以正确的顺序执行。它与其他处理器看到一个处理器的写入需要多长时间无关。

    关于c# - 我不了解 volatile 和 Memory-Barrier 的是,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21989776/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com