Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - [120]

Шрифт
Интервал

Такие оптимизации корректны, только если память ведет себя нормально. “Особая” память так себя не ведет. Наиболее распространенным видом особой памяти, вероятно, является память, используемая для отображенного на память ввода-вывода.. Вместо чтения и записи обычной памяти, местоположения в такой особой памяти в действительности сообщаются с периферийными устройствами, например внешними датчиками или мониторами, принтерами, сетевыми портами и т.п. Давайте с учетом этого снова рассмотрим код с, казалось бы, избыточными чтениями:

>auto y = x; // Чтение x

>y = x;      // Чтение x еще раз

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

Похожа ситуация с записями, кажущимися излишними. Например, если в коде

>x = 10; // Запись x

>х = 20; // Запись x еще раз

переменная x соответствует управляющему порту радиопередатчика, может оказаться, что этот код выполняет некоторые команды с радиопередатчиком, и значение 10 соответствует команде, отличной от имеющей код 20. Оптимизация, убирающая первое присваивание, могла бы изменить последовательность команд, отправляемых радиопередатчику.

Квалификатор >volatile представляет собой способ сообщить компиляторам, что мы имеем дело с такой особой памятью. Для компилятора это означает “не выполняй никаких оптимизаций над операциями с этой памятью”. Так что если переменная >x соответствует особой памяти, она должна быть объявлена как >volatile:

>volatile int x;

Рассмотрим влияние этого квалификатора на последовательность нашего исходного кода:

>auto y = x; // Чтение x

>y = x;      // Чтение x еще раз (не может быть устранено)

>x = 10;     // Запись x (не может быть устранена)

>x = 20;     // Запись x еще раз

Это именно то, чего мы хотим, когда >x представляет собой отображение в память (или отображается в ячейке памяти, совместно используемой разными процессами и т.п.).

Вопрос на засыпку: какой тип у в последнем фрагменте кода: >int или >volatile int[28]?

Тот факт, что кажущиеся избыточными загрузки и бессмысленные сохранения должны оставаться на месте при работе с особой памятью, объясняет, кстати, почему для такого рода работы не подходят объекты >std::atomic. Компиляторам разрешается устранять такие избыточные операции у >std::atomic. Код написан не в точности так же, как и для >volatile, но если мы на минуту отвлечемся от этого и сосредоточимся на том, что компиляторам разрешается делать, то можно сказать, что концептуально компиляторы могут, получив код

>std::atomic<int> x;

>auto y = x; // Концептуально читает x (см. ниже)

>y = x;      // Концептуально читает x еще раз (см. ниже)

>x = 10;     // Записывает x

>х = 20;     // Записывает x еще раз

оптимизировать его до

>auto y = x; // Концептуально читает x (см. ниже)

>x = 20;     // Записывает x

Очевидно, что это неприемлемое поведение при работе с особой памятью.

Но если >x имеет тип >std::atomic, ни одна из этих инструкций компилироваться не будет:

>auto y = x; // Ошибка!

>y = x;      // Ошибка!

Дело в том, что копирующие операции в >std::atomic удалены (см. раздел 3.5). И не зря. Рассмотрим, что произошло бы, если бы инициализация у значением x компилировалась. Поскольку >x имеет тип >std::atomic, тип у был бы также выведен как >std::atomic (см. раздел 1.2). Ранее я отмечал, что одна из лучших возможностей >std::atomic заключается в атомарности всех их операций, но чтобы копирующее конструирование >y из >x было атомарным, компиляторы должны генерировать код для чтения >x и записи >y как единую атомарную операцию. В общем случае аппаратное обеспечение не в состоянии это сделать, так что копирующее конструирование типами >std::atomic не поддерживается. Копирующее присваивание удалено по той же причине, а потому присваивание >x переменной >y также не компилируется. (Перемещающие операции не объявлены в >std::atomic явно, так что в соответствии с правилами генерации специальных функций, описанных в разделе 3.11, >std::atomic не предоставляет ни перемещающего конструирования, ни перемещающего присваивания.)

Можно получить значение >x в переменную >y, но это требует использования функций- членов >load и >store типа >std::atomic. Функция-член >load атомарно считывает значение >std::atomic, а функция-член >store атомарно его записывает. Для инициализации >y значением >x, после которой выполняется размещение значения >x в >y, код должен иметь следующий вид:

>std::atomic y(x.load()); // Чтение x

>y.store(x.load());            // Чтение x еще раз

Этот код компилируется, но тот факт, что чтение >x (с помощью >x.load()) является отдельным от инициализации или сохранения значения вызовом функции, делает очевидным то, что нет никаких оснований ожидать, что целая инструкция будет выполняться как единая атомарная операция.

Компиляторы могут “оптимизировать” приведенный код, сохраняя значение >x в регистре вместо двойного его чтения:

>register = x.load();          // Чтение x в регистр


>std::atomic y(register); // Инициализация y

>                              // значением регистра

>y.store(


Еще от автора Скотт Мейерс
Эффективное использование STL

В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

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


Симуляция частичной специализации

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


Обработка событий в С++

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


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.