Моя перзистентная система

Обсуждаются как существующие проекты (перевод документации, информационная система и т.п.), так и создание новых.

Модератор: Модераторы

Ваше мнение:

Мне как раз такой и не хватало!
2
8%
Полезная вещь.
11
44%
Для меня, увы, неприменима.
10
40%
Бесполезна.
2
8%
 
Всего голосов : 25

Re: Моя перзистентная система

Сообщение Cheb » 21.12.2008 21:26:02

0.8.98
Добавлена поддержка полей-метаклассов.
В тестовом примере демонстрируется простейшая фабрика классов. При чтении в "старой версии программы" неизвестный метакласс успешно приводится к метаклассу известного предка.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Re: Моя перзистентная система

Сообщение Cheb » 10.02.2009 22:30:19

Если есть желающие на своей шкуре испытать нововведения следующей версии - милости прошу.
Официально пока не выпустил: нет времени на тщательное тестирование и проверку под Дельфи.

- TTrulyPersistent переименован в TManagedObject
- добавлен сборщик мусора
- переименованы некоторые вспомогательные типы
- биты 30 и 31 поля CpsMask зарезервированы за сборщиком мусора
- удалён массив MemoryLeakSuspects, все объекты опущеные при чтении теперь сдаются сборщику мусора на разбирательство.
- на практике подтверждена способность Chepersy сохранять и загружать в/из потоков сжатия.

((удалена ссылка на устаревший файл))

Что не тестировал: как раз сборщик мусора. Пока просто не на чем.

Выдержка из доки:
СБОРЩИК МУСОРА
(или «нам только C# не хватало»)

Сборщик мусора - мощнейшее средство обеспечения безопасности, не позволяющее окончательно удалить объект пока на него ссылаются другие объекты (пример: бодал баран козла. Но пока он разгонялся, козла удалили. И словили мы Access Violation при вызове метода Баран.Боднуть(Баран.Козёл). А со сборщиком мусора ничего не случится: дохлый козёл будет лежать на кладбище пока баран не забудет о нём, и будет выметен из памяти при очередной сборке мусора.

Но цена безопасности высока: периодически нужен полный обход графа, а он при большом количестве объектов может занять неприемлемо большое время.

Сборщик мусора лежит в глобальной переменной GarbageCollector (инициализируется автоматически при старту Chepersy).

По умолчанию сборщик ведает только теми объектами, у которых вы вызвали метод Scrape, и теми, что были опущены при чтении.

Если установить глобальную переменную CpsFullManagement в true (что происходит автоматом при включённом кондишнле safeloading), то деструктор TmanagedObject.Destroy будет вызывать Scrape вместо унаследованного Destroy - таким образом, ни один объект не будет удаляться, все будут поступать на кладбище сборщика мусора. Также деструктор никогда не работает у объектов, помеченных как Scraped (выброшенные на помойку) - удалить эти объекты может только сборщик мусора обойдя весь граф и убедившись, что они Orphaned (бесхозные) - т.е. что на них не ссылается ни один объект графа.

По умолчанию, сборщик мусора не запускается автоматически, и scraped объекты будут накапливаться пока у вас не кончится память. Существуют два пути:

1. Вручную вызывать GarbageCollector.Collect() когда вам удобно. В качестве единственного необязательного параметра этот метод принимает 64-битное целое, задающее ограничение по времени в тактах процессора, согласно формату ассемблерной команды RDTSC. Естественно, обход графа разделить на части невозможно, он займёт столько времени сколько займёт, независимо от ограничения по времени. Так что механизм ограничения времени полезен если у вас много объектов с тяжёлыми деструкторами (например, записывающие что-то в файл при удалении). Возможен вариант, когда вызов Collect() закончится одним обходом графа, а ни один объект удалён не будет: это действие будет отложено до следующего вызова Collect().

2. Установить GarbageCollector.Autorun.Enabled, a также следующие параметры записи Autorun:
NumObjectsToActivate - размер кладбища, при достижении которого включается автоматическая сборка мусора (при вызове метода Add() );
NumObjectsToIgnoreTimeConstraint - размер кладбища при превышении которого котором будет игнорироваться ограничение по времени;
TimeConstraint - ограничение по времени.

В любом случае вы должны указать сборщику мусора корневой объект своего графа в GarbageCollector.GraphRoot. Пока это поле равно nil, метод Collect() только дочищает объекты, ранее помеченные как бесхозные, а при отсутствии таковых - ничего не делает. Поле GraphRoot никогда не выставляется автоматом - вы должны сами заботиться, чтобы оно содержало корень вашего графа. Иначе будет беда.

Размер кладбища можно узнать посредством GarbageCollector.GraveyardCount.
Последний раз редактировалось Cheb 23.02.2010 18:53:59, всего редактировалось 1 раз.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Re: Моя перзистентная система

Сообщение Cheb » 23.02.2010 18:53:05

Официально выпустил 0.8.99 и выложил на SourceForge.
- всё вышеперечисленное
- доработана совместимость с FPC 2.4.0 (не работала под линухом из-за изменений rtti по WideString)
- поддержка Дельфи отправилась лесом (переставил винду на той машине лицензионную, ключи от турбы потёрлись, бекапов я, как настоящий джедай, никогда не делаю)

http://www.chebmaster.narod.ru/soft/libs_pers.html
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Re: Моя перзистентная система

Сообщение Cheb » 26.11.2014 19:41:32

Надеялся уже скоро выпустить 0.9.00, но вспомнил про один фатальный недостаток:

При обходе дерева экземпляры классов метятся путём записи в поле индекса внутри самого экземпляра.
Это настолько недружественно к кэшу, что просто тошно :x Любой оптимизированный скриптовый язык сможет нарезать круги вокруг Паскаля, если я продолжу писать подобный говнокод :evil:

Поэтому, следующая версия... опять откладывается. :oops:
Буду решать проблему радикально: заведу для TManagedObject собственный менеджер памяти, где для каждого размера экземпляров класса будет отдельный пул. Каждый пул будет иметь *компактные массивы* индексов для дерева и битовых флагов (Легко писать мемори менеджер когда все возможные размеры блоков известны заранее, да). Первым полем экземпляра класса будут не флаги, а индекс в его пуле. Изменение флагов больше *НЕ* будет приводить к записи поперёк разрежённого массива данных, где на каждый объект при обходе дерева приходится персональный кэш-мисс :evil:

Не знаю, осилю ли в этом году, если честно. :( Постараюсь заодно дожать сборщик мусора и сделать вменяемую поддержку многопоточности. В смысле, у каждого потока будет персональный набор пулов памяти. Не столько для избежания лишней блокировки, сколько чтобы каждый объект знал, какому потоку он принадлежит. Чтобы можно было, например, сохранять часть дерева в файл, пока другие части продолжают обрабатываться в других потоках (чтобы, например, поймать случай непредусмотренного пересечения этих ветвей дерева, и красиво упасть). :roll:

P.S. Напоролся на эти недостатки когда начал уже трахаться с многопоточной загрузкой ресурсов в своём игровом движке: типа, один поток сосёт из PK3 файла, второй в это время расшифровывает засосанные жпеги, третий масштабирует текстуры и пакует в атласы, а основной - запихивает получившееся безобразие в текстуры :roll: FPSы при этом держатся на стабильных 30, и крутится анимация "ждите, грузюсь".
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Re: Моя перзистентная система

Сообщение Cheb » 29.11.2014 05:43:44

[Epic facepalm]

Достал исходник старого-старого теста Чеперси, портировал на нынешнююю версию.
Итог (структура и объём данных идентичные):

2006 год. Хилая для того времени машина, искусственно замедленная до 1 гигагерца.
Время загрузки - около 2.2 микросекунды на объект.

2014 год. Современный ультрабук пальцы веером.
Время загрузки - около 7 микросекунд на объект.

Что же я нахимичил то? :cry: Ведь сплошные улучшения.
Вот что значит несколько лет ковыряться с тестовым набором данных из пары дюжин объектов :(
Пора опять за буттлнеками гоняться с выбивалкой для ковров :evil:

Добавлено спустя 13 часов 13 минут 12 секунд:
Я убью TFileStream! :evil: Подстерегу в тёмной подворотне, и замордую ржавым, тупым тапочком! :evil:
Естественно, про буферизацию оно даже не слышало. При записи одного байтика сразу бежит вызывать Win32 API, конкретно - WriteFile
Короче. Подменил прямое обращение к TFileStream на TMemoryStream, который потом уже сливается в файловый поток одним вызовом метода SaveToStream.
Даже такая неэффективная двойная сопля ускорила сериализацию на порядок! :evil:
Теперь сохранение занимает 800 наносекунд на объект вместо 9000. Из них примерно две трети - запись структуры данных в TMemoryStream, и одна треть - слив в файл, на 280 Мб/с, что уже похоже на производительность SSD диска.

Вывод: буду восстанавливать делать внутреннюю буферизацию, которую в своё время похерил, понадеявшись на TStream. Авось, ещё немного выжму.

Добавлено спустя 1 час 55 минут 34 секунды:
F*** YEAH! :twisted:
Чтение - 500 наносекунд на объект, запись - 320 наносекунд. 8)

И это при том, что структура данных намеренно неэффективная (цепочка из десятков тысяч объектов, соединённых полями следующий/предыдущий). Стек насилуется страшно, рекурсия такая, что 50Мб еле хватает.

Мдя... Тридцатикратное ускорение благодаря всего лишь тупой буферизации, слабанной за час на коленке. 8)
Вы таки не поверите, насколько MOVE(,,4) оказалась быстрее, чем Stream.WriteDword()... :roll:
Код: Выделить всё
procedure ReadFromBuffer(var v; size: longint); inline;
var a: longint;
begin
  if BufferHigh < 0 then begin
    if size >= CpsBufferSize then begin
      {$ifdef bench_streaming}{$include un_bottleneck_begin}{$endif}
      CpsStream.Read(v, size);
      {$ifdef bench_streaming}{$include un_bottleneck_end}{$endif}
      Exit;
    end;
    FillBuffer;
  end;
  if BufferPos + size > BufferHigh + 1 then begin
    MOVE(CpsBuffer[BufferPos], pointer(@v)^, BufferHigh + 1 - BufferPos);
    if (size - (BufferHigh + 1 - BufferPos)) > CpsBufferSize then begin
      {$ifdef bench_streaming}{$include un_bottleneck_begin}{$endif}
      CpsStream.Read((pointer(@v) + BufferHigh + 1 - BufferPos)^, size - (BufferHigh + 1 - BufferPos));
      {$ifdef bench_streaming}{$include un_bottleneck_end}{$endif}
      BufferPos:= 0;
      BufferHigh:= -1;
      Exit;
    end;
    a:= BufferHigh + 1 - BufferPos;
    FillBuffer;
    MOVE(CpsBuffer[0], (pointer(@v) + a)^, size - a);
    BufferPos:= size - a;
  end
  else begin
    MOVE(CpsBuffer[BufferPos], v, size);
    BufferPos+= size;
  end;
end; 
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Re: Моя перзистентная система

Сообщение Cheb » 11.01.2016 05:19:08

Бросаю обратную совместимость.

Причина: никто не пользуется (скорей всего), последний раз я выкладывал в 2008-м и та была не слишком стабильная + разгрёб старые залежи говнокода, поведение сериализации слегка изменилось - и прочитать старые файлы не получается ни с каким бубном :evil:

Было слишком много ктулхуфтагнов и рекурсии чтобы докопаться до истины.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Re: Моя перзистентная система

Сообщение Cheb » 14.10.2016 18:41:59

Перевёл статус проекта в "заброшен": https://sourceforge.net/projects/chepersy/
Причина - несовместимость со строками фпц 3.

Буду отпочковывать Чеперси 2 от игрового движка с нуля, когда оный взлетит, наконец.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Re: Моя перзистентная система

Сообщение runewalsh » 14.10.2016 22:54:14

А что со строками не так? У меня были непонятки с типом констант и хранением UTF-8 и произвольных кусков памяти в string, но уже разобрался (они по умолчанию «UTF-8-в-raw» и всё работает как раньше, пока не добавляешь BOM или $codepage utf8), в остальном просто пара дополнительных полей, не?

Upd: А кодировку можно задать (в т. ч. занулить, получив старое поведение) через SetCodePage(..., {convert} false).
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Re: Моя перзистентная система

Сообщение Cheb » 27.11.2016 18:58:05

Изменения накапливаются, дело давно уже не только в строках.
Основная причина - отказался от менеджед объектов со сборщиком мусора, перевёл на объекты со счётчиком ссылок.
Ну, и говнокода повыгреб столько, что обратная совместимость накрылась.
Планируется ещё одна оптимизация, которая забъёт последний гвоздь в крышку гроба:
сейчас, когда у записей/классов встречается поле типа запись, статический массив или массив, индексируемый перечислиимым типом, они разлагаются на составляющие, и у родительской записи/класса регистрируются поля с именами наподобие
a
b
c.a
c.b
d[eklmn_empty]
d[eklmn_first]
- и т.п.
Это просто, надёжно, но пространство имён засирается страшно. Боюсь, на любом реальном применении начнёт тормозить парсинг заголовка потока, а регистрация - занимать совершенно неприличное время (перезагрузка игровой DLL станет ощутимой на глаз).

Надо:
а) сделать так, чтобы подобные поля регистрировались как одно поле и при конверсии обрабатывались по одному
б) заодно добавить поддержку изменения размера статических массивов ИЛИ изменения размера их элементов: сейчас сохранёнки 64-битной и 32-битной версий не совместимы из-за этой банальности.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 994
Зарегистрирован: 06.06.2005 15:54:34

Пред.

Вернуться в Разное

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 23

Рейтинг@Mail.ru