2013-10-10 6 views
16

У меня проблема с компилятором 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). Затем я припарковал их в коде при соответствующем документировании основных (не) требований переупорядочения.

+0

+1 хорошо изучил вопрос. – ldrumm

+0

Ум, вы не представляете, насколько это было болезненно исследовано: - { –

+1

Является ли SET_BUSY чем-то большим, чем '& =', или этот код защищен мьютексом или иным образом сделан потокобезопасным? Потому что если вы пытаетесь синхронизировать потоки, используя то, что в основном равно 'if (! Busy) {busy = true; DoStuff(); busy = false; } 'то это не будет потокобезопасным независимо от порядка. – user2802841

ответ

6

Так, как я понимаю, pNext = plog2sizeChunk->pNext публикует блок, чтобы его могли видеть другие темы, и вы должны убедиться, что они видят правильный флаг занятости.

Это означает, что вам необходимо однонаправленную защиту памяти до публикации ее публикации (также перед ее чтением в другом потоке, хотя если ваш код работает на x86, вы получаете их бесплатно), чтобы убедиться, что потоки фактически видят изменение. Перед записью вам также понадобится один, чтобы избежать переупорядочения записей после него. Нет просто вставлять сборку или использовать стандартную совместимую волатильность (MSVC volatile дает дополнительные гарантии, хотя это имеет значение здесь) не - да, это останавливает компилятор от сдвига чтения и записи, но ЦП не связан им и может сделать то же переупорядочение внутри страны.

Оба MSVC и gcc имеют встроенные/макросы для создания барьеров памяти (see eg here). MSVC также дает более сильные гарантии для летучих, которые достаточно хороши для вашей проблемы. Наконец, атомистика C++ 11 будет работать, но я не уверен, что у C есть какой-либо переносной способ гарантировать барьеры памяти.

+0

Зачем мне нужен барьер памяти, чтобы предотвратить переупорядочение записей? Архитектура x86 гарантирует, что записи выполняются в программном порядке. Согласны, когда код смешивания читает и записывает, мне нужен барьер памяти, чтобы предотвратить прохождение чтения от критических записей. –

+0

@Ira Да x86 имеет очень сильные требования к заказу. Это означает, что многие барьеры памяти являются nop для этого ISA true. Я просто думаю, что лучше написать правильный код для всех архитектур и позволить авторам-компиляторам беспокоиться о том, какие барьеры являются неявными для данной архитектуры, а какие нет. – Voo

+0

Время присуждать за ответ. Спасибо за вашу помощь. –

3

См. _ReadWriteBarrier. Это встроенный компилятор, посвященный тому, что вы ищете. Обязательно проверьте документацию на точной версии MSVC («устаревший» на VS2012 ...). Остерегайтесь процессора переупорядочения (тогда см MemoryBarrier

документация states, что встроенные функции компилятора _ReadBarrier, _WriteBarrier и _ReadWriteBarrier (компилятор переназначения) и что MemoryBarrier макро (CPU переназначения) все «устаревшим», начиная с VS2012. Но я думаю, что они будет продолжать нормально работать в течение некоторого времени ...

Новый код может использовать новый C++ 11 объектов (ссылки на странице MSDN)

+0

_WriteBarrier бесполезен, потому что: '_ReadBarrier, _WriteBarrier и _ReadWriteBarrier компиляторы не позволяют компилятору переупорядочивать только'. – Voo

+0

Я знаю это, см. Мое упоминание о переупорядочении процессора? Я просто ответил на вопрос OP ... – manuell

+0

Да, но: 'Это встроенный компилятор, посвященный тому, что вы ищете' явно ошибочен, потому что он * не * решает проблемы OPs, так зачем вообще упоминать _WriteBarrier? – Voo

0

Я хотел бы использовать летучее ключевое слово. Это предотвратит компилятор переупорядочение инструкций. http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword

+0

+1 Спасибо, это помогло. Я нашел ответ @ manuell в ReadWriteBarrier очень полезным и меньше кувалдой. –

Смежные вопросы