2016-08-03 2 views
6

Попытка продолжить с моей идеей, что с использованием как программных, так и аппаратных ограничений памяти я мог бы отключить оптимизацию вне порядка для конкретной функции внутри кода, который скомпилирован с оптимизацией компилятора , и поэтому я мог бы реализовать программное обеспечение семафор с использованием алгоритмов, такими как Peterson или Deker, что не требует внеочередного исполнения, я тестировал следующий код, который содержит как SW барьерный asm volatile("": : :"memory") и ССАГПЗ предопределённого HW барьер __sync_synchronize:Использование барьеров памяти для принудительного выполнения в порядке

#include <stdio.h> 
int main(int argc, char ** argv) 
{ 
    int x=0; 
    asm volatile("": : :"memory"); 
    __sync_synchronize(); 
    x=1; 
    asm volatile("": : :"memory"); 
    __sync_synchronize(); 
    x=2; 
    asm volatile("": : :"memory"); 
    __sync_synchronize(); 
    x=3; 
    printf("%d",x); 
    return 0; 
} 

Но выходной файл компиляции:

main: 
.LFB24: 
    .cfi_startproc 
    subq $8, %rsp 
    .cfi_def_cfa_offset 16 
    mfence 
    mfence 
    movl $3, %edx 
    movl $.LC0, %esi 
    movl $1, %edi 
    xorl %eax, %eax 
    mfence 
    call __printf_chk 
    xorl %eax, %eax 
    addq $8, %rsp 

И если удалить барьеры и скомпилировать снова, я получаю:

main 
.LFB24: 
    .cfi_startproc 
    subq $8, %rsp 
    .cfi_def_cfa_offset 16 
    movl $3, %edx 
    movl $.LC0, %esi 
    movl $1, %edi 
    xorl %eax, %eax 
    call __printf_chk 
    xorl %eax, %eax 
    addq $8, %rsp 

и скомпилирован с gcc -Wall -O2 в Ubuntu 14.04.1 LTS, x86.

Ожидаемый результат заключался в том, что выходной файл кода, который содержит барьеры памяти, будет содержать все присвоения значений, которые у меня есть в моем исходном коде, с ними mfence.

Согласно связанному StackOverflow пост -

gcc memory barrier __sync_synchronize vs asm volatile("": : :"memory")

Добавляя ассемблерные на каждой итерации, НКУ не разрешается изменять порядок операций мимо барьера

И далее:

Howe ver, когда ЦП выполняет этот код, разрешается переупорядочивать операции «под капотом», если он не разбивает память модели . Это означает, что выполнение операций может быть выполнено не в порядке (если процессор поддерживает это, как и большинство в наши дни). A HW ограждение предотвратило бы это.

Но как вы можете видеть, единственное различие между кодом с барьерами памяти и кода без них является то, что бывший один содержит так, как я не ожидал увидеть его mfence, а не все задания включены.

Почему выходной файл файла с барьерами памяти был не таким, каким я ожидал? Почему порядок mfence был изменен? Почему компилятор удалил некоторые из заданий? Предоставляет ли компилятор такую ​​оптимизацию, даже если применяется барьер памяти и разделяет каждую строку кода?

Ссылки на типы барьерных памяти и использования:

+1

Терминология: ** Нарушение исполнения отличается от памяти переупорядочивание **. Даже процессоры в заказе конвейерны и извлекают выгоду из буфера хранилища, особенно для магазинов, которые пропускают L1. (https://en.wikipedia.org/wiki/MESI_protocol#Memory_Barriers). Как только они, как известно, не спекулятивны, их можно отслеживать только логикой упорядочения памяти (для принудительного упорядочивания StoreStore и LoadStore, если это необходимо), пока они на самом деле зафиксировать кэш L1 после того, как трубопровод забыли о них.) 'MFENCE' не сериализует конвейер; он только сериализует порядок, когда операции памяти становятся глобально видимыми. –

ответ

4

барьеры памяти сказать компилятор/процессор, что инструкция не следует переупорядочивать через барьер, они не означают, что записи, которые могут быть доказаны бессмысленно, должны быть выполнены в любом случае.

Если вы определяете ваш x, как volatile, компилятор не может сделать предположение о том, что это единственная организация, которая заботится о x s значение и должен следовать правилам абстрактной машины C, что для записи в память на самом деле.

В вашем конкретном случае вы могли бы пропустить барьеры, потому что уже гарантировано, что изменчивые обращения не переупорядочиваются друг против друга.

Если у вас есть поддержка C11, вам лучше использовать _Atomic s, что дополнительно может гарантировать, что обычные присвоения не будут переупорядочены относительно вашего x и что обращения являются атомарными.


EDIT: GCC (как и лязг) кажутся несовместимыми в связи с этим и не всегда делают это optimizaton. I opened a GCC bug report regarding this.

+3

Вы написали гораздо лучший ответ, чем я. – 2501

+0

Правильный ответ. Я протестировал его сейчас с 'volatile', и код с барьером памяти был скомпилирован так, как я ожидал (в то время как код без барьера памяти был по-прежнему оптимизирован). К сожалению, я не могу проверить «атомный», поскольку у меня нет поддержки C11. – user2162550

+0

@ 2501 Спасибо. Не стесняйтесь расширять его, если вы думаете, что что-то можно улучшить. :) – a3f

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