Автор Статьи: Vadim Godunko
Дата: 10-09-2010
Статья с сайта http://ru.ada-community.org/
Для решения некоторых задач на современных многопроцессорных и многоядерных вычислительных машинах требуются счётчики, значение которых может увеличиваться и уменьшаться одновременно из нескольких задач, при этом не разрушая значения и не затирая изменённое другой задачей значение.
В качестве примера будем рассматривать тип данных-счётчик, инициализируемый начальным значением 1, и три операции над ним:
Increment - увеличивает значение счётчика на единицу;
Decrement - уменьшает значение счётчика на единицу, возвращает True если значение достигло нуля, в противном случае возвращает False;
Is_One - проверяет текущее значение счётчика, возвращает True если оно равно единице, в противном случае возвращает False.
Поставленная задача может быть решена встроенными средствами языка, например так
with Interfaces;package Counters istype Counter is limited private;procedure Increment (Self : in out Counter);function Decrement (Self : not null access Counter) return Boolean;function Is_One (Self : Counter) return Boolean;privateprotected type Counter isprocedure Increment;procedure Decrement (Is_Zero : out Boolean);function Is_One return Boolean;privateValue : Interfaces.Unsigned_32 := 1;end Counter;end Counters;
Unknown format: ada
В этом варианте реализации для защиты текущего значения счётчика используется защищённый тип. Это пожалуй самый портируемый вариант решения, однако обладающий серьёзным недостатком - низкой эффективностью, поскольку реализация защищённых типов как правило использует механизмы межпроцессного взаимодействия.
Значительно интереснее было бы использовать аппаратную поддержку операций атомарного инкремента/декремента, присутствующую в большинстве современных процессоров. Это, однако, требует включения в код ассемблерных вставок, что приводит к невозможности легкого портирования кода. Тем не менее, пользователи компилятора GNAT имеют возможность использовать встроенные функции GCC для выполнения атомарных операций инкремента/декремента. Их использование не делает код портируемым между GNAT-ом и другим компилятором, но позволяет использовать его на большом количестве современных процессоров (среди которых Alpha, IA64, PowerPC, SPARC V9, X86-64).
Семейство встроенных функций GCC для инкремента именуется как __sync_add_and_fetch_X, а для декремента значения как __sync_sub_and_fetch_X, где X - размер операнда в байтах. В Ada эти операции могут быть импортированы с указанием соглашения Intrinsic:
Unknown format: ada
Использование встроенных функций требует особого внимания к некоторым аспектам объявления и использования переменной-счётчика. Это затрагивает:
выравнивания объекта в памяти - некоторые процессоры накладывают жесткие ограничения на выравнивание данных в памяти и неспособны выполнить операции чтения/записи невыровненных данных;
обеспечения доступа к значению одной неразрывной операцией - необходимо гарантировать, что процессор способен выполнять чтение/запись одной неразрывной операцией, в противном случае возможно получение некорректных данных при чтении или искажение данных при записи;
предотвращения оптимизации кода сохранением значения в регистрах и последующим его использованием. ;
Стандартные средства языка Ada предоставляют портируемые способы решения этих задач:
для обеспечения корректного выравнивания объекта в памяти достаточно объявить переменную как aliased;
проверка поддержки неразрывного доступа к значению и использование соответствующих операций гарантируется использованием прагмы Atomic;
оптимизация кода путём сохранения ранее прочитанного значения в регистрах отключается применение прагмы Volatile;
Прежде чем показать полный код реализации полезно обратить внимание на один небольшой момент - приватный тип Counter объявлен как запись с одним компонентом для хранения значения. Это необходимо для гарантии корректного выравнивания, и снятия с пользователя необходимости контроля за выравниванием объектов Counter; а так же для обеспечения возможности начальной инициализации значения счётчика.
А теперь приведём полный код реализации:
Unknown format: ada Unknown format: ada
Приведённая реализация с использованием встроенных функций GCC будет успешно работать при использовании компилятора GNAT GPL 2009 и выше. При компиляции кода для процессора x86 в 32-битном режиме необходимо активировать режим генерации кода для процессора 80486 и выше (указав в ключ компилятора -march=i486, хотя в общем случае можно порекомендовать использовать ключ -march=i686).
Для особо пытливых приводятся фрагменты кода подпрограммы Decrement для процессоров x86_64 и SPARC V9 соответственно: