В процессе портирования кода с C++ на Дельфи очень часто приходиться портировать код, использующий STL коллекции. Набор коллекций, предлагаемый в составе Дельфи весьма аскетичен и поэтому иногда сложно подобрать подходящую замену. Иногда сталкиваешься с кодом где объект, размещён в стеке или использует собственный аллокатор памяти.
Мне очень не нравиться предлагаемый режим обновления данных. Чтобы обновить данные мне нужно извлечь из коллекции помещённое в неё значение, обновить значение и потом обратно поместить изменённое значение. Это требует как минимум двух дополнительных операций копирования. Мы не можем передать элемент данных как var параметр в процедуру.
Для коллекции нет возможности поменять способ выделения памяти. Память для объектов и записей выделяется из одной общей кучи. После использования память надо аккуратно вернуть в кучу. Правильное освобождение памяти не всегда является тривиальной задачей и на неё тратится и время процессора, и время программиста на написание этого кода. В STL для всех видов коллекций можно указать свой аллокатор памяти.
Данная реализация опирается на записи и указатели. Пока, я не вижу возможности реализовать то что я хочу с использованием стандартных объектов. Создание и уничтожения объектов использует общую кучу памяти. Нет возможности размещения объектов в стеке вызова. Старый добрый object объявлен deprecated и добавление новых возможностей для этого типа не поддерживается.
Данная реализация коллекций опирается на механизм управления памятью на основе типизированных регионов памяти. Использование типизированных регионов памяти позволяет упростить решение ряда задач:
- Код освобождения памяти. Задача освобождения памяти становиться проще и может быть выполнена гораздо быстрее.
- Параллельное программирование. Общеизвестный факт - стандартный менеджер памяти должен быть потоко-защищённым. В любой момент времени только один поток может обращаться к менеджеру памяти. Выделение и освобождение памяти используют механизмы взаимного исключения и это не быстрая операция, особенно при значительной дефрагментации памяти. При использования отдельного типизированного региона памяти мы обращаемся к стандартном менеджеру памяти только в момент увеличения требуемой памяти и удаления структуры после её использования.
Поддержка основных структур с возможностью указать аллокатор памяти. Доступ к элементам списка производится через указатели. Как правило память для значений размещается в так называемом сегментированном регионе памяти, которые в процессе работы не будут перемещаться. При необходимости увеличить память региона, для него выделяется дополнительный сегмент памяти. Это означает, что мы к элементам данных размещённым в таком регионе можем обращаться через указатель.
Для массивов мы используем так называемый непрерывный регион памяти. Обращение к элементам данных производиться через индекс. При необходимости увеличить память региона, для него выделяется сегмент с большим размером и данные из текущего сегмента памяти копируются в новый сегмент. После копирования данных прежний сегмент будет удалён.
В конечном счёте работать через указатели очень удобно и эффективно. Код становиться гораздо проще и лаконичнее. Однако, если нет опыта работы с указателями легко "выстрелить себе в ногу". Для любителей инкапсуляции, можно нужную структуру агрегировать в качестве приватного поля. Далее мы открываем только необходимую часть интерфейса путём переопределения в public секции нужных методов и свойств. Если поставить опцию inline мы избежим дополнительных затрат. Компилятор Дельфи не будет генерировать код для переопределённых методов. В месте вызова метода будет непосредственный вызов метода агрегированной структуры.
TsgTuple<T1, ...>
Generic TuplesTsgArray<T>
Generic Array with memory allocation from a shared memory regionTsgList<T>
Generic List of ValuesTsgRecordList<T>
Generic List of Values accessed by pointerTsgLinkedList<T>
Generic Bidirectional Linked ListTsgForwardList<T>
Generic Unidirectional Linked ListTsgHashMap<Key, T>
Generic Unordered dictionaryTsgMap<Key, T>
Generic Ordered Dictionary based on 2-3 treeTsgSet<Key>
Generic Set based on 2-3 trees
TsgPointerArray
Untyped List of PointersTsgPointerList
Untyped List of Values accessed by pointerTCustomLinkedList
Untyped Bidirectional Linked ListTsgCustomTree
Untyped Dictionary based on 2-3 trees
Мы начали добавление Delphi итераторов.
Теперь мы можем использовать конструкцию for p in List do ;
Самое интересное, для реализации итератора мы используем record и это работает!
По сравнению с использованием объектов генерируемый код гораздо эффективнее и что приятно,
нет обращений к куче, переменная для итератора располагается в стеке.
Для меня это оказалось достаточно приятной неожиданностью!
Возможностью указать аллокатор памяти также означает что мы работаем в основном с записями. Как правило некоторая структура использует один и более регион памяти, который является простым менеджером памяти. После использования структуры, у нас есть возможность вернуть всю память занятую память освободив регион памяти. У нас есть ограничения с использованием наследования. Наследование в некоторых случаях мы можем заменить агрегацией и хелперами. Как правило для реализации коллекций, это не является проблемой. Использование записей позволяет располагать коллекции в стеке. Иногда это очень удобно.
Пул объектов позволяет управлять повторным использованием структур, когда создание объектов требует больших затрат памяти или может быть создано ограниченное количество объектов некоторого типа.
Если мы что-то перестаём использовать не всегда стоит удалять структуру или объект. Частенько объект приходится создавать повторно.