У меня проблема с компилятором MS C, переупорядочивающим определенные операторы, критичные в контексте многопоточности, на высоких уровнях оптимизации. Я хочу знать, как заставить порядок в определенных местах, при этом используя высокий уровень оптимизации. (При низких уровнях оптимизации, этот компилятор не переставляет заявления)Форсировать порядок выполнения операторов 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 fetch.
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]
I обратите внимание, что оборудование x86 может изменить порядок этих конкретных инструкций в любом случае, поскольку они не относятся к одному и тому же местоположение памяти и чтение могут передавать записи; чтобы действительно исправить это пример, мне нужен какой-то барьер памяти. Вернемся к моему более раннему замечанию, если они оба записывают, x86 не будет их изменять, и порядок записи будет показан в этом порядке другими потоками. Поэтому в этом случае я не думаю, что мне нужен барьер памяти, просто принудительный заказ.
Я еще не видел, что компилятор переупорядочил две записи (пока), но я не очень много смотрел (пока); Я просто споткнулся об этом. И, конечно же, с оптимизацией только потому, что вы не видите ее в этой компиляции, это не значит, что она не появится в следующем.
Итак, как заставить компилятор заказать их?
Насколько я понимаю, я могу объявить слоты памяти в структуре неустойчивыми. Они по-прежнему независимых местах хранения, поэтому я не вижу, как это предотвращает оптимизацию.Может быть, я неправильно истолковываю, что такое летучие средства?
EDIT (20 октября): благодаря всем респондентам. В моей текущей реализации используется volatile (используется как начальное решение), _ReadWriteBarrier (чтобы отметить код, в котором переупорядочение не должно выполняться компилятором) и несколько MemoryBarriers (где происходят чтения и записи), и это, похоже, решило проблему ,
EDIT: (2 ноября): Чтобы быть чистым, я определил множество макросов для ReadBarrier, WriteBarrier и ReadWriteBarrier. Существуют комплекты для предварительной и пост-блокировки, до и после разблокировки и общего использования. Некоторые из них являются пустыми, некоторые содержат _ReadWriteBarrier и MemoryBarrier, как это подходит для x86 и типичных блокировок на основе XCHG [XCHG включает неявный MemoryBarrier, таким образом устраняя эту необходимость в блокировке pre-/post-sets). Затем я припарковал их в коде при соответствующем документировании основных (не) требований переупорядочения.
+1 хорошо изучил вопрос. – ldrumm
Ум, вы не представляете, насколько это было болезненно исследовано: - { –
Является ли SET_BUSY чем-то большим, чем '& =', или этот код защищен мьютексом или иным образом сделан потокобезопасным? Потому что если вы пытаетесь синхронизировать потоки, используя то, что в основном равно 'if (! Busy) {busy = true; DoStuff(); busy = false; } 'то это не будет потокобезопасным независимо от порядка. – user2802841