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

问题描述 投票:0回答:1

TL;DR:我理解MOVNTI操作相对于程序的其他部分是不排序的,所以需要SFENCEMFENCE。但是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真的放松了,以至于最新的代码不正确,那么内存写入要重新排序的原因是什么?

c++ x86 memory-barriers
1个回答
2
投票

movnti 存储也是弱相对有序的。 在asm中,你肯定需要 sfence 存储数据后,以获得释放语义的存储到 producer_index,无论你是用 movnti 或普通 mov 存储器。

它可能会发生工作的大部分时间,单独的存储不会变得对其他线程可见,直到使用NT存储的一些全行写入之后。 事实上很有可能:完成一个缓存行会触发WC缓冲区到DRAM的刷新(绕过驱逐缓存),但索引绝对不会是一个全行存储,除非它恰好与写入的数据末尾相邻。

在C++中,这意味着使用 _mm_sfence() 在你做任何事情之前,存储到 producer_index.


请注意,使用 movnti 对于单个标量来说是一个非常糟糕的主意。它迫使缓存行从缓存中被驱逐,这样读取器就必须一路从DRAM中获取数据,也就是说,它将增加该控制变量的核心间延迟,否则可能会在L3中发生。

只有当你期望完成一整行缓存,并且你不期望另一个线程很快就会重新加载数据时,才使用NT存储。

© www.soinside.com 2019 - 2024. All rights reserved.