Типы данных с копированием‐при‐модификации
простой вариант
Автор: Vadim Godunko
Дата: 16.09.2012
Типы данных с копированием‐при‐модификации находят широкое применение в современной программной индустрии с целью упрощения управления и минимизации объёма использованной динамической памяти. Менее известными, но подчас более важными свойствами таких типов данных является константное время выполнения операции присваивания и использование небольшого и независящего от фактического размера данных объёма памяти стэка, используемой для объектов.
Для реализации обычно используется технология неявного совместного использования данных, когда объект хранит только ссылку на сегмент совместно используемых данных, а управление освобождением сегмента совместно используемых данных осуществляется с использованием счётчика ссылок. В общем случае эта технология имеет одно важное ограничение, не позволяющее активно использовать её в Ada программах — сегмент совместно используемых данных не должен использоваться более чем одной задачей. Для снятия этого ограничения достаточно защитить счётчик ссылок от одновременного доступа несколькими задачами. В предыдущей статье обуждался вопрос эффективной реализации атомарного счётчика ссылок с достаточной степенью переносимости, что даёт возможность создания эффективной реализации типов данных с копированием‐при‐модификации.
В качестве примера будем рассматривать упрощённый аналог стандартного контейнера Ada.Containers.Holders, предназначенный для хранения одного значения определённого при настройке типа данных. Для хранения данных будем использовать структуру данных, состоящую из двух полей: атомарного счётчика ссылок и значения. Над ссылочным типом на эту структуру необходимо иметь три базовые операции:
- Reference, увеличивающую счётчик ссылок на единицу;
- Unreference, уменьшающую счётчик ссылок на единицу и освобождающую совместно используемый объект при достижении счётчиком нулевого значения;
- Detach, выполняющую создание копии объекта для последующей модификации.
#ada
type Shared_Data is record
Counter : aliased Counters.Counter;
Value : T;
end record;
type Shared_Data_Access is access all Shared_Data;
procedure Reference (Self : not null Shared_Data_Access);
procedure Unreference (Self : in out Shared_Data_Access);
procedure Detach (Self : in out not null Shared_Data_Access);
Реализация подпрограммы Reference содержит исключительно вызов подпрограммы увеличения значения счётчика. Для исключения необходимости проверки факта переполнения счётчика предполагается, что он способен хранить достаточно большое значение достигнуть которое невозможно:
#ada
procedure Reference (Self : not null Shared_Data_Access) is
begin
Counters.Increment (Self.Counter);
end Reference;
Реализация подпрограммы Unreference вызывает функцию декремента значения счётчика и освобождает задействованный под совместно используемые данные сегмент при достижении счётчиком нулевого значения; в противном случае она просто сбрасывает переданный указатель на значение null (то же самое делает и вызов функции Free):
#ada
procedure Unreference (Self : in out Shared_Data_Access) is
procedure Free is
new Ada.Unchecked_Deallocation (Shared_Data, Shared_Data_Access);
begin
if Counters.Decrement (Self.Counter'Access) then
Free (Self);
else
Self := null;
end if;
end Unreference;
Подпрограмму Detach можно реализовать весьма просто, как создание нового объекта совместно используемых данных с инициализацией имеющимся значением:
#ada
procedure Detach (Self : in out not null Shared_Data_Access) is
Old : Shared_Data_Access := Self;
begin
Self := new Shared_Data'(Counter => <>, Value => Old.Value);
Unreference (Old);
end Detach;
Интерфейс настраиваемого пакета состоит из типа‐контейнера, константы этого типа, представляющей «пустое» значение и подпрограмм для установки и получения значения; тип‐контейнер является контролируемым и для него переопределяются стандартные подпрограммы Adjust (копирование) и Finalize (деструктор):
#ada
private with Ada.Finalization;
private with Counters;
generic
type T is private;
package Holders is
type Holder is private;
Empty : constant Holder;
procedure Set (Self : in out Holder; Value : T);
function Get (Self : Holder) return T;
private
...
type Holder is new Ada.Finalization.Controlled with record
Data : Shared_Data_Access := new Shared_Data;
end record;
overriding procedure Adjust (Self : in out Holder);
overriding procedure Finalize (Self : in out Holder);
Empty : constant Holder := (Ada.Finalization.Controlled with others => <>);
end Holders;
Тип Holder содержит одно единственное поле — ссылку на совместно испольуемый объект данных. Это поле автоматически инициализируется при создании объекта типа Holder ссылкой на выделенный совместно используемый объект. Поскольку, согласно соглашению, счётчик автоматически инициализируется значением 1 нет необходимости увеличивать его значение и переопределять подпрограмму Initialize.
Подпрограмма Adjust выполняет вызов описанной выше подпрограммы Reference:
overriding procedure Adjust (Self : in out Holder) is
begin
Reference (Self.Data);
end Adjust;
Подпрограмма Finalize выполняет вызов подпрограммы Unreference, но необходимо помнить, что язык позволяет осуществлять вызов этой подпрограммы многократно (что имеет место быть в условиях хитросплетённых ситуаций очистки при выявлении непредвиденного поведения программы), поэтому перед вызовом Unreference внутренняя ссылка проверяется на ненулевое значение (значение указателя сбрасывается в null при выполнении подпрограммы Unreference):
#ada
overriding procedure Finalize (Self : in out Holder) is
begin
if Self.Data /= null then
Unreference (Self.Data);
end if;
end Finalize;
Подпрограмма Get возвращает хранящееся в совместно используемом объекте значение.
#ada
function Get (Self : Holder) return T is
begin
return Self.Data.Value;
end Get;
Подпрограмма Set устанавливает значение внутри совместно используемого объекта, но перед этим выполняет вызов подпрограммы Detach для создания собственной копии данных.
#ada
procedure Set (Self : in out Holder; Value : T) is
begin
Detach (Self.Data);
Self.Data.Value := Value;
end Set;
Как видно из примера, шаблон реализации типов с копированием‐при‐модификации достаточно прост. Отметим, что рассмотренная реализация может быть усовершенствована для повышения производительности, эффективности использования памяти, возможности использования в пакетах категории Preelaborate, а в некоторых случаях и для использования Preelaborable_Initialization типов. Вопросы возможной оптимизации будут рассмотрены в следующей статье.
В заключение приводится полный исходный текст примера:
private with Ada.Finalization;
private with Counters;
generic
type T is private;
package Holders is
type Holder is private;
Empty : constant Holder;
procedure Set (Self : in out Holder; Value : T);
function Get (Self : Holder) return T;
private
type Shared_Data is record
Counter : aliased Counters.Counter;
Value : T;
end record;
type Shared_Data_Access is access all Shared_Data;
procedure Reference (Self : not null Shared_Data_Access);
procedure Unreference (Self : in out Shared_Data_Access);
procedure Detach (Self : in out not null Shared_Data_Access);
type Holder is new Ada.Finalization.Controlled with record
Data : Shared_Data_Access := new Shared_Data;
end record;
overriding procedure Adjust (Self : in out Holder);
overriding procedure Finalize (Self : in out Holder);
Empty : constant Holder
:= (Ada.Finalization.Controlled with others => <>);
end Holders;
#ada
with Ada.Unchecked_Deallocation;
package body Holders is
------------
-- Adjust --
------------
overriding procedure Adjust (Self : in out Holder) is
begin
Reference (Self.Data);
end Adjust;
------------
-- Detach --
------------
procedure Detach (Self : in out not null Shared_Data_Access) is
Old : Shared_Data_Access := Self;
begin
Self := new Shared_Data'(Counter => <>, Value => Old.Value);
Unreference (Old);
end Detach;
--------------
-- Finalize --
--------------
overriding procedure Finalize (Self : in out Holder) is
begin
if Self.Data /= null then
Unreference (Self.Data);
end if;
end Finalize;
---------
-- Get --
---------
function Get (Self : Holder) return T is
begin
return Self.Data.Value;
end Get;
---------------
-- Reference --
---------------
procedure Reference (Self : not null Shared_Data_Access) is
begin
Counters.Increment (Self.Counter);
end Reference;
---------
-- Set --
---------
procedure Set (Self : in out Holder; Value : T) is
begin
Detach (Self.Data);
Self.Data.Value := Value;
end Set;
-----------------
-- Unreference --
-----------------
procedure Unreference (Self : in out Shared_Data_Access) is
procedure Free is
new Ada.Unchecked_Deallocation (Shared_Data, Shared_Data_Access);
begin
if Counters.Decrement (Self.Counter'Access) then
Free (Self);
else
Self := null;
end if;
end Unreference;
end Holders;
|