Оптимизация постоянного сворачивания / распространения с барьерами памяти

Некоторое время я читал, чтобы лучше понять, что происходит при многопоточном программировании с современным (многоядерным) процессором. Тем не менее, пока я читал этот, Я заметил код ниже в разделе «Явные барьеры компилятора», который не использует volatile для IsPublished Глобальный.

#define COMPILER_BARRIER() asm volatile("" ::: "memory")

int Value;
int IsPublished = 0;

void sendValue(int x)
{
Value = x;
COMPILER_BARRIER();          // prevent reordering of stores
IsPublished = 1;
}

int tryRecvValue()
{
if (IsPublished)
{
COMPILER_BARRIER();      // prevent reordering of loads
return Value;
}
return -1;  // or some other value to mean not yet received
}

Вопрос в том, безопасно ли исключать летучие для IsPublished Вот? Многие люди упоминают, что ключевое слово «volatile» не имеет ничего общего с многопоточным программированием, и я с ними согласен. Тем не менее, во время оптимизации компилятора «Constant Folding / Propagation» может применяться и как вики-страница показывает, что можно изменить if (IsPublished) в if (false) если компилятор не знает много о том, кто может изменить значение IsPublished, Я что-то здесь упускаю или неправильно понял?

Барьеры памяти могут помешать упорядочению компилятора и неупорядоченному выполнению для CPU, но, как я сказал в предыдущем параграфе, мне все еще нужно volatile чтобы избежать «константного свертывания / распространения», которое является опасной оптимизацией, особенно с использованием глобалов в качестве флагов в коде без блокировки?

0

Решение

Если tryRecvValue() вызывается один раз, его можно опустить летучий за IsPublished, То же самое верно в случае, когда между вызовами tryRecvValue() есть вызов функции, для которого компилятор не может доказать, что он не меняется ложный ценность IsPublished,

// Example 1(Safe)
int v = tryRecvValue();
if(v == -1) exit(1);

// Example 2(Unsafe): tryRecvValue may be inlined and 'IsPublished' may be not re-read between iterations.
int v;
while(true)
{
v = tryRecvValue();
if(v != -1) break;
}

// Example 3(Safe)
int v;
while(true)
{
v = tryRecvValue();
if(v != -1) break;
some_extern_call(); // Possibly can change 'IsPublished'
}

Постоянное распространение может применяться только тогда, когда компилятор может доказать значение переменной. Так как IsPublished объявлен как непостоянный, его значение может быть доказано, только если:

  1. Переменная присваивается данному значению или же За чтением из переменной следует ветвь, выполняемая только в том случае, если переменная имеет значение.
  2. Переменная читается (снова) в поток той же программы.

  3. Между 2 и 3 переменная не изменяется в потоке данной программы.

Если вы не позвоните tryRecvValue() в какой-то функции .init компилятор никогда не увидит IsPublished инициализация в том же потоке с его чтением. Итак, доказательство ложный Значение этой переменной согласно ее инициализации невозможно.

доказывания ложный ценность IsPublished в соответствии с ложный (пустая) ветка в tryRecvValue функция возможна, см. Example 2 в коде выше.

0

Другие решения


По вопросам рекламы [email protected]