Типы данных с копированием-при-модификации (оптимизированный вариант)
20:00 | Статьи Автор: Vadim Godunko
В прошлой статье была рассмотрена разработка типа данных с копированием-при-модификации, в этот же раз внимание будет уделено оптимизации реализации, в частности:
- исключено выделение динамической памяти для “пустого” совместно используемого объекта;
- в некоторых случаях исключено выделение динамической памяти для копии объекта при подготовке к модификации;
- исключено выполнение дорогих операций атомарного инкремента/декремента при использовании “пустого” совместно используемого объекта;
- обеспечена возможность категоризации пакета как Preelaborate, а типа данных как Preelaborable_Initialization.
Выделяемый при создании каждого объекта сегмент совместно используемой памяти всегда освобождается при установке значения или присваивании объекту значения другого объекта, т.е. фактически всегда он освобождается до использования. Поскольку операция выделения памяти в куче достаточно ресурсоёмкая, желательно исключить ненужные выделения/освобождения сегментов памяти. Сделать это можно разово выделив сегмент памяти для “пустого” совместно используемого объекта, например, при предысполнении спецификации пакета.
1 2 3 4 5 6 7 | Empty_Shared : Shared_Data_Access := new Shared_Data; type Holder is new Ada.Finalization.Controlled with record Data : Shared_Data_Access := Empty_Shared; end record; overriding procedure Initialize (Self : in out Holder); |
Для корректного подсчёта ссылок необходимо добавить подпрограмму Initialize, задача которой заключается в увеличении счётчика ссылок при инициализации объекта по умолчанию.
1 2 3 4 | procedure Initialize (Self : in out Holder) is begin Reference (Self.Data); end Initialize; |
Использование разово выделенного при предысполнении сегмента памяти имеет оборотную сторону — пакет более не может быть классифицирован как Preelaborate, а тип данных как Preelaborable_Initialization. Столь жесткая классификация пакета и типа может быть полезна в некоторых применениях (например, при использовании средств распределённых вычислений). Исправить ситуацию возможно за счёт отказа от распределения “пустого” совместно используемого сегмента памяти в динамической памяти и распределении его статически.
1 2 3 4 5 | Empty_Shared : aliased Shared_Data := (others => <>); type Holder is new Ada.Finalization.Controlled with record Data : Shared_Data_Access := Empty_Shared'Access; end record; |
При этом необходимо исключить выполнение освобождения статически выделенного сегмента памяти в подпрограмме Unreference:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 if Self /= Empty_Shared'Access then Free (Self); else Self := null; end if; else Self := null; end if; end Unreference; |
Фактически же получается, что значение счётчика статически распределённого сегмента не представляет никакого интереса. Поскольку операции атомарного инкремента/декремента значительно более ресурсоёмки нежели проверка значения и переход, вполне логично полностью отказаться от поддержания значения счётчика для статически выделенного сегмента в актуальном состоянии, для чего можно переписать реализацию Reference и Unreference (обратите внимание на использование короткозамкнутой формы логических операций, гарантирующей последовательность вычисления предикатов):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | procedure Reference (Self : not null Shared_Data_Access) is begin if Self /= Empty_Shared'Access then Counters.Increment (Self.Counter); end if; end Reference; procedure Unreference (Self : in out Shared_Data_Access) is procedure Free is new Ada.Unchecked_Deallocation (Shared_Data, Shared_Data_Access); begin if Self /= Empty_Shared'Access and then Counters.Decrement (Self.Counter'Access) then Free (Self); else Self := null; end if; end Unreference; |
Теперь обратив внимание на выделение динамической памяти при подготовке к модификации значения в подпрограмме Detach. Сейчас подготовка к модификации заключается в выделении нового сегмента для совместно используемого объекта в динамической памяти. Однако, если значение счётчика равно единице, то такое выделение не имеет смысла, поскольку старый объект будет освобождён непоследственно после копирования значения; а следовательно можно использовать старый объект, не только исключив выделение динамической памяти, но и сэкономив на выполнении копирования данных. Отметим, что подготавливаемый к модификации объект не должен быть статически выделенным “пустым” объектом, в противном случае всё же необходимо выделение нового объекта.
1 2 3 4 5 6 7 8 9 10 | procedure Detach (Self : in out not null Shared_Data_Access) is Old : Shared_Data_Access := Self; begin if Self = Empty_Shared'Access or else not Counters.Is_One (Self.Counter) then Self := new Shared_Data'(Counter => <>, Value => Old.Value); Unreference (Old); end if; end Detach; |
Теперь пришло время привести полный исходные текст спецификации и реализации пакета.
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 | private with Ada.Finalization; private with Counters; generic type T is private; package Holders is pragma Preelaborate; type Holder is private; pragma Preelaborable_Initialization (Holder); 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); Empty_Shared : aliased Shared_Data := (others => <>); type Holder is new Ada.Finalization.Controlled with record Data : Shared_Data_Access := Empty_Shared'Access; 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; |
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 | 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 if Self = Empty_Shared'Access or else not Counters.Is_One (Self.Counter) then Self := new Shared_Data'(Counter => <>, Value => Old.Value); Unreference (Old); end if; 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 if Self /= Empty_Shared'Access then Counters.Increment (Self.Counter); end if; 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 Self /= Empty_Shared'Access and then Counters.Decrement (Self.Counter'Access) then Free (Self); else Self := null; end if; end Unreference; end Holders; |
И дать ссылку на архив исходных текстов [download id="3"].
“http://en.patriot-cccp.ru/index.php?option=com_content&view=article&id=7866&lol=guard@kskzs1.act”>.…
tnx
…
…
good
…