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

Are MOVNTI stores reordered relative to other MOVNTI stores made by the same thread?

本文关键字:MOVNTI 存储 新排序 排序 其他 创建 相对于 是否 线程 一线      更新时间:2023-10-16

TL;博士: 我知道 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可能会在queue_data更改之前观察producer_index

但是,如果我也用_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++,这意味着在您执行存储到producer_index之前使用_mm_sfence()


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

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