В параллельных программах программисты уделяют особое внимание синхронизации данных между различными процессами или потоками. Особенно, когда несколько потоков одновременно изменяют одну и ту же переменную, необходимо принять надежную синхронизацию или другие меры, чтобы гарантировать правильное изменение данных. Дело здесь в следующем: не предполагайте порядок выполнения инструкций. Вы не можете предсказать порядок, в котором будут выполняться инструкции между различными потоками.
Но в однопоточной программе нам обычно легко предположить, что инструкции выполняются последовательно, иначе мы можем представить, какие ужасные изменения произойдут с программой. Идеальная модель такова: порядок выполнения различных инструкций уникален и упорядочен. Этот порядок — это порядок, в котором они записаны в коде, независимо от процессора или других факторов. Эта модель называется моделью последовательной согласованности. Это модель, основанная на системе фон Неймана. Конечно, это предположение само по себе разумно и на практике редко встречается аномально, но на самом деле ни одна современная многопроцессорная архитектура не принимает эту модель, потому что она слишком неэффективна. В оптимизации компиляции и конвейере ЦП почти все они связаны с переупорядочением инструкций.
переупорядочение времени компиляции
Типичное переупорядочение во время компиляции заключается в корректировке порядка инструкций, чтобы максимально сократить количество операций чтения и сохранения регистров без изменения семантики программы, а также полностью повторно использовать сохраненные значения регистров.
Предположим, первая инструкция вычисляет значение, присваивает его переменной A и сохраняет его в регистре. Вторая инструкция не имеет ничего общего с A, но ей необходимо занять регистр (при условии, что она займет регистр, в котором находится A). Инструкция использует значение A и не имеет ничего общего со второй инструкцией. Тогда, если согласно модели последовательной согласованности, A помещается в регистр после выполнения первой инструкции, A больше не существует, когда выполняется вторая инструкция, и A снова считывается в регистр, когда выполняется третья инструкция, и во время В этом процессе значение A не изменилось. Обычно компилятор меняет местами вторую и третью инструкции, так что A существует в регистре в конце первой инструкции, а затем значение A можно прочитать непосредственно из регистра, уменьшая накладные расходы на повторное чтение.
Значение изменения порядка для конвейера
Почти все современные процессоры используют механизм конвейера для ускорения обработки инструкций. Вообще говоря, для обработки инструкции требуется несколько тактов процессора, и благодаря параллельному выполнению конвейера несколько инструкций могут выполняться за один и тот же такт. Метод просто изложен. Просто разделите инструкции на разные. Цикл выполнения, такой как чтение, адресация, синтаксический анализ, выполнение и другие этапы, обрабатываются в разных компонентах. В то же время в исполнительном блоке EU функциональный блок разделен на разные компоненты, такие как компоненты сложения, компоненты умножения. и компоненты загрузки, элементы хранения и т. д. могут дополнительно реализовать параллельное выполнение различных вычислений.
Архитектура конвейера требует, чтобы инструкции выполнялись параллельно, а не так, как это предусмотрено в последовательной модели. Переупорядочение способствует полному использованию конвейера, тем самым достигая суперскалярных эффектов.
Обеспечьте порядок
Хотя инструкции не обязательно выполняются в том порядке, в котором мы их написали, нет сомнений, что в однопоточной среде конечный эффект выполнения инструкции должен соответствовать ее эффекту при последовательном выполнении, иначе такая оптимизация потеряет значение.
Обычно вышеуказанные принципы будут соблюдаться независимо от того, выполняется ли переупорядочение инструкций во время компиляции или во время выполнения.
Переупорядочение в модели хранения Java
В модели памяти Java (JMM) переупорядочение является очень важным разделом, особенно в параллельном программировании. JMM обеспечивает семантику последовательного выполнения с помощью правила «происходит до». Если вы хотите, чтобы поток, выполняющий операцию B, наблюдал за результатами потока, выполняющего операцию A, то A и B должны удовлетворять принципу «происходит до». В противном случае JVM может выполнять произвольные действия. операции над ними для улучшения производительности программы.
Ключевое слово Летучие может обеспечить видимость переменных, поскольку все операции с изменчивыми выполняются в основной памяти, а основная память используется всеми потоками. Цена здесь заключается в том, что производительность приносится в жертву, а регистры или кэш не могут использоваться, поскольку они не являются глобальными. , видимость не может быть гарантирована, и может произойти некорректное чтение.
Другая функция Летучих — локально предотвращать переупорядочение. Операционные инструкции для изменчивых переменных не будут переупорядочены, поскольку при переупорядочении могут возникнуть проблемы с видимостью.
С точки зрения обеспечения видимости, блокировки (включая явные блокировки, блокировки объектов), а также чтение и запись атомарных переменных могут обеспечить видимость переменных. Однако методы реализации немного отличаются. Например, блокировка синхронизации обеспечивает повторное считывание данных из памяти для обновления кэша при получении блокировки. При снятии блокировки данные записываются обратно в память для обеспечения безопасности. что данные видны, в то время как летучие переменные просто читают и записывают память.