代码之家  ›  专栏  ›  技术社区  ›  Alex Guteniev

MOVNTI存储是否相对于同一线程创建的其他MOVNTI存储器重新排序?

  •  0
  • Alex Guteniev  · 技术社区  · 5 年前

    太长,读不下去了 我理解MOVNTI操作不是相对于程序的其余部分排序的,因此需要SFENCE/MFENCE。 但是,MOVNTI操作是否相对于同一线程的其他MOVNTI运算没有排序?


    假设我有一个生产者-消费者队列,我想在生产者端使用MOVNTI来避免缓存污染。

    (实际上还没有观察到缓存污染效应,所以现在可能是理论问题)

    因此,我将替换以下制作人:

    std::atomic<std::size_t> producer_index;
    QueueElement queue_data[MAX_SIZE];
    ...
    void producer()
    {
        for (;;)
        {
            ...
    
            queue_data[i].d1 = v1;
            queue_data[i].d2 = v2;
            ...
            queue_data[i].dN = vN;
    
            producer_index.store(i, std::memory_order_release);
        }
    }
    

    具有以下内容:

    void producer()
    {
        for (;;)
        {
            ...
    
            _mm_stream_si64(&queue_data[i].d1, v1);
            _mm_stream_si64(&queue_data[i].d2, v2);
            ...
            _mm_stream_si64(&queue_data[i].dN, vN);
    
            _mm_sfence();
    
            producer_index.store(i, std::memory_order_release);
        }
    }
    

    我添加的通知 _mm_sfence ,这将等到“非时间”操作结果变得可观察到。 如果我不添加它, consumer 可以观察 producer_index 之前 queue_data 变化。

    但是,如果我用以下方式编写索引呢 _mm_stream_si64 也?

    std::size_t producer_index_value;
    std::atomic_ref<std::size_t> producer_index { producer_index_value };
    
    void producer()
    {
        for (;;)
        {
            ...
    
            _mm_stream_si64(&queue_data[i].d1, v1);
            _mm_stream_si64(&queue_data[i].d2, v2);
            ...
            _mm_stream_si64(&queue_data[i].dN, vN);
    
            _mm_stream_si64(&producer_index_value, i);
        }
    }
    

    根据我对英特尔手册的阅读, 这应该行不通 ,因为非临时商店放宽了订购。

    但他们不是说“放松”只是为了使非时间操作不与程序的其他部分相冲突吗? 也许它们是内在有序的,所以 producer 仍然会按预期工作吗?

    如果MOVNTI真的放松了,导致最新代码不正确,那么内存写入被重新排序的原因是什么?

    0 回复  |  直到 5 年前
        1
  •  2
  •   Peter Cordes    5 年前

    movnti 商店之间的秩序也很弱。在asm中,你绝对需要 sfence 在存储数据以获取存储的发布语义之后 producer_index ,无论你是否这样做 莫文蒂 或平原 mov 商店。

    在大多数情况下,使用NT存储进行一些整行写入后,单独的存储才会对其他线程可见,这可能是有效的。事实上,很可能:完成缓存行会触发WC缓冲区向DRAM的刷新(绕过/驱逐缓存),但索引肯定不会是整行存储,除非它恰好与写入的数据末尾相邻。

    在C++中,这意味着使用 _mm_sfence() 在你做任何储存之前 生产者索引 .


    请注意,使用 莫文蒂 因为单个标量是一个非常糟糕的主意 :它强制从缓存中删除缓存行,因此读取器必须一直从DRAM中获取它。也就是说,它将增加该控制变量的内核间延迟,否则可能会在L3中命中。

    仅当您希望完成整个缓存行,并且不希望另一个线程很快重新加载数据时,才使用NT存储。