我在 MS C 编译器重新排序某些语句时遇到问题,这在多线程上下文中至关重要,在高级别的优化中。我想知道如何在仍然使用高级优化的同时强制在特定位置进行排序。(在低优化级别,此编译器不会重新排序语句)
以下代码:
ChunkT* plog2sizeChunk=...
SET_BUSY(plog2sizeChunk->pPoolAndBusyFlag); // set "busy" bit on this chunk of storage
x = plog2sizeChunk->pNext;
产生这个:
0040130F 8B 5A 08 mov ebx,dword ptr [edx+8]
00401312 83 22 FE and dword ptr [edx],0FFFFFFFEh
其中对 pPoolAndBusyFlag 的写入由编译器重新排序以在 pNext 提取之后发生。
SET_BUSY 本质上是
plog2sizeChunk->pPoolAndBusyFlag&=0xFFFFFFFeh;
我认为编译器正确地决定重新排序这些访问是可以的,因为它们是针对同一结构的两个不同成员的,并且这种重新排序对单线程执行的结果没有影响:
typedef struct chunk_tag{
unsigned pPoolAndBusyFlag; // Contains pointer to owning pool and a busy flag
natural log2size; // holds log2size of the chunk if Busy==false
struct chunk_tag* pNext; // holds pointer to next block of same size
struct chunk_tag* pPrev; // holds pointer to previous block of same size
} ChunkT, *pChunkT;
出于我的目的,必须先设置 pPoolAndBusyFlag,然后才能在多线程/多核上下文中对该结构的其他访问有效。我不认为这种 特殊的访问对我有问题,但是编译器可以重新排序这意味着我的代码的其他部分可能具有相同类型的重新排序,但在这些地方可能很关键。(想象这两个语句是对两个成员的更新,而不是一写/一读)。我希望能够强制执行操作的顺序。
理想情况下,我会写如下内容:
plog2sizeChunk->pPoolAndBusyFlag&=0xFFFFFFFeh;
#pragma no-reordering // no such directive appears to exist
pNext = plog2sizeChunk->pNext;
我已经通过实验验证了我可以以这种丑陋的方式获得这种效果:
plog2sizeChunk->pPoolAndBusyFlag&=0xFFFFFFFeh;
asm { xor eax, eax } // compiler won't optimize past asm block
pNext = plog2sizeChunk->pNext;
给
0040130F 83 22 FE and dword ptr [edx],0FFFFFFFEh
00401312 33 C0 xor eax,eax
00401314 8B 5A 08 mov ebx,dword ptr [edx+8]
我注意到 x86 硬件可能会重新排序这些特定指令,因为它们不引用相同的内存位置,并且读取可能会通过写入;要真正修复此示例,我需要某种类型的内存屏障。回到我之前的评论,如果它们都是写入,x86 不会重新排序它们,其他线程将按照该顺序看到写入顺序。所以在那种情况下,我认为我不需要内存屏障,只需要强制排序。
我还没有看到编译器重新排序两次写入(还),但我还没有很努力地寻找(还);我刚刚被这个绊倒了。当然,优化只是因为你在这个编译中没有看到它并不意味着它不会出现在下一个。
那么,我如何强制编译器订购这些?
我知道我可以将结构中的内存插槽声明为易失性。它们仍然是独立的存储位置,所以我看不出这如何阻止优化。也许我误解了 volatile 的含义?
编辑(10 月 20 日):感谢所有响应者。我当前的实现使用 volatile(用作初始解决方案)、_ReadWriteBarrier(标记编译器不应该发生重新排序的代码)和一些 MemoryBarriers(发生读取和写入的地方),这似乎已经解决了问题.
编辑:(11 月 2 日):为了干净,我最终定义了 ReadBarrier、WriteBarrier 和 ReadWriteBarrier 的宏集。有用于前后锁定、前后解锁和一般用途的套装。其中一些是空的,一些包含 _ReadWriteBarrier 和 MemoryBarrier,适用于 x86 和基于 XCHG 的典型自旋锁 [XCHG 包含一个隐式 MemoryBarrier,因此避免了对锁前集/后集的需要)。然后,我将这些放在代码中记录基本(非)重新排序要求的适当位置。