Барьеры оптимизации и барьеры памяти
Работая с оптимизирующими компиляторами, вы не можете предполагать как само собой разумеющееся, что операторы будут выполняться именно в том порядке, в каком они идут в исходном коде. Например, компилятор может переставить ассемблерные инструкции так, чтобы регистры использовались оптимальным образом. Кроме того, современные процессоры обычно выполняют несколько инструкций параллельно и могут изменить порядок обращения к памяти. Такое переупорядочивание может сильно ускорить работу программы.
Однако в тех случаях, когда дело касается синхронизации, переупорядочива- ния инструкций следует избегать. Если инструкция, расположенная после примитива синхронизации, будет выполнена раньше этого примитива, путаница неизбежна. Поэтому все примитивы синхронизации действуют как барьеры оптимизации и барьеры памяти.
Барьер оптимизации гарантирует, что ассемблерные инструкции, соответствующие операторам языка С, помещенным до примитива, не будут перемешаны компилятором с инструкциями, соответствующими операторам языка С, поставленным после примитива. В Linux макрос barrier о, который расширяется в asm volatile (ffff::: "memory"), действует как барьер оптимизации. Инструкция asm заставляет компилятор вставить ассемблерный фрагмент (в данном случае пустой). Ключевое слово volatile запрещает компилятору переставлять инструкцию asm по отношению к другим инструкциям программы. Ключевое слово memory вынуждает компилятор предполагать, что все ячейки памяти были изменены ассемблерными инструкциями, и он поэтому не может оптимизировать код, используя содержимое ячеек, сохраненное в регистрах процессора до выполнения инструкции asm. Обратите внимание: барьер оптимизации не гарантирует, что ассемблерные инструкции не будут "перетасованы" процессором, это задача барьера памяти.
Барьер памяти гарантирует, что операции, помещенные до примитива, будут завершены до начала выполнения операций, расположенных после примитива. Таким образом, барьер памяти работает как брандмауэр, сквозь который не могут пройти ассемблерные инструкции.
В процессорах 80x86 следующие ассемблерные инструкции обеспечивают "сериализацию", поскольку они действуют как барьеры памяти:
- все инструкции, работающие с портами ввода/вывода;
- все инструкции, имеющие байт lock в качестве префикса
- все инструкции, которые пишут в управляющие регистры, системные регистры и регистры отладки (например, инструкции cli и sti, изменяющие состояние флага if в регистре efiags);
- ассемблерные инструкции lfence, sfence и mfence, появившиеся в микропроцессорах Pentium 4 для эффективной реализации барьеров чтения памяти, записи в память и чтения/записи соответственно;
- несколько специальных ассемблерных инструкций, в том числе инструкция iret, завершающая обработчик прерывания или исключения.
Эти примитивы работают и как барьеры оптимизации, поскольку они должны не позволить компилятору перемещать ассемблерные инструкции вокруг барьера. Барьеры "чтения памяти" действуют только на инструкции, читающие данные из памяти, а барьеры "записи в память" влияют только на инструкции, пишущие в память. Барьеры памяти могут быть полезны как в однопроцессорных, так и в многопроцессорных системах. Примитивы smp_xxx применяются, когда барьер памяти должен предотвратить состояния гонки, возникающие только в многопроцессорных системах; в однопроцессорных системах эти инструкции эффекта не имеют. Остальные барьеры памяти используются для предотвращения конфликтов одновременного обращения, возникающих как в однопроцессорных, так и в многопроцессорных системах.
Реализация барьеров памяти зависит от архитектуры системы. В архитектуре 80x86 макрос mb о обычно развертывается в оператор asm volatile!"ifепсе"), если процессор поддерживает ассемблерную инструкцию lfence, ИЛИ В asm volatile("lock;addl $0,0 (esp)":::"memory") в противном случае. Оператор asm вставляет ассемблерный фрагмент в код, сгенерированный компилятором, и действует как барьер оптимизации. Ассемблерная инструкция lock; addl $0,0(esp) прибавляет ноль к верхнему элементу стека. Сама по себе она бесполезна, но префикс lock превращает ее в барьер памяти для процессора.
Макрос wmb , в принципе, проще, поскольку расширяется в макрос barrier о. Это сделано, потому что существующие микропроцессоры Intel не переупорядочивают операции записи в память, и, следовательно, нет необходимости вставлять в код сериализующую ассемблерную инструкцию. Однако этот макрос не позволяет компилятору перетасовывать инструкции.
Заметим, что в многопроцессорных системах все атомарные операции, описанные ранее в этой главе, работают барьерами памяти, поскольку имеют префиксный байт lock.
Предыдущая страница | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | Следующая страница