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真的放松了,以至于最新的代码不正确,那么内存写入要重新排序的原因是什么?
movnti
存储也是弱相对有序的。 在asm中,你肯定需要 sfence
存储数据后,以获得释放语义的存储到 producer_index
,无论你是用 movnti
或普通 mov
存储器。
它可能会发生工作的大部分时间,单独的存储不会变得对其他线程可见,直到使用NT存储的一些全行写入之后。 事实上很有可能:完成一个缓存行会触发WC缓冲区到DRAM的刷新(绕过驱逐缓存),但索引绝对不会是一个全行存储,除非它恰好与写入的数据末尾相邻。
在C++中,这意味着使用 _mm_sfence()
在你做任何事情之前,存储到 producer_index
.
请注意,使用 movnti
对于单个标量来说是一个非常糟糕的主意。它迫使缓存行从缓存中被驱逐,这样读取器就必须一路从DRAM中获取数据,也就是说,它将增加该控制变量的核心间延迟,否则可能会在L3中发生。
只有当你期望完成一整行缓存,并且你不期望另一个线程很快就会重新加载数据时,才使用NT存储。