Страницы

Поиск по вопросам

пятница, 14 февраля 2020 г.

Можно ли сохранить экземпляр TDictionary целиком, включая хэши?

#delphi #оптимизация #хеширование #словари #биоинформатика


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

type
  PosIndex = packed record
     chr, pos:integer;
  end;
  PosIndexData = record
    gname, rname, promoter: string;
    count:array[0..NOfTissues-1] of byte;
  end;

TPosDict = class (TDictionary)
 private
   procedure SaveToStream(stream: TStream);
   procedure LoadFromStream(stream: TStream);
 public
   procedure SaveToFile(filename:string);
   procedure LoadFromFile(filename:string);
   procedure LoadFromZip(AFileName, InnerName: string);
   procedure SaveToZip(AFileName, InnerName: string);
end;


Предупреждая вопросы и комментарии в стиле "Зачем нужен TDictionary, когда есть базы
данных?", сразу скажу: у нас мобильное (не в плане телефона, а в плане, что оно часто
запускается где попало) приложение, мы не можем использовать стационарный сервер БД,
как коллега в своём вопросе Как оптимизировать таблицы/запрос в MySQL?, а работа с
файловыми БД с нашими объёмами данных, увы, крайне медленна. А вот с TDictionary поиск
происходит пусть не мгновенно, но для нас вполне подходяще по времени.

Запись в поток (этот метод затем используют и SaveToFile и SaveToZip) происходит так:

procedure TPosDict.SaveToStream(stream: TStream);
var
  writer: TWriter;
  ps:PosIndex;
  pid:PosIndexData;
  l:integer;
begin
  writer := TWriter.Create(stream, 4096);
  l:=sizeof(pid.count);
  try
    writer.WriteListBegin;
    for ps in Self.Keys do
      begin
        pid:=Items[ps];
        writer.WriteInteger(ps.chr);
        writer.WriteInteger(ps.pos);
        writer.WriteString(pid.gname);
        writer.WriteString(pid.rname);
        writer.WriteString(pid.promoter);
        writer.Write(pid.count,l);
      end;
    writer.WriteListEnd;
  finally
    writer.Free;
  end;
end;


Метод быстр, гигабайтные данные сохраняются быстро даже в ZIP-файл. А вот считывание
из файла крайне медленно из-за того, что данные добавляются во вновь созданный TDictionary,
происходит хэширование и проверка на уникальность:

procedure TPosDict.LoadFromStream(stream: TStream);
var
  reader: TReader;
  ps:PosIndex;
  pid:PosIndexData;
  l:integer;

begin
  Clear;
  l:=sizeof(pid.count);
  reader := TReader.Create(stream, 9192);
  try
    reader.ReadListBegin;
    while not reader.EndOfList do
    begin
       ps.chr:=reader.ReadInteger;
       ps.pos:=reader.ReadInteger;
       pid.gname:=reader.ReadString;
       pid.rname:=reader.ReadString;
       pid.promoter:=reader.ReadString;
       reader.Read(pid.count,l);
       Add(ps,pid); // вот это всё тормозит!!!
    end;
    reader.ReadListEnd;
  finally
    reader.Free;
  end;
end;


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


Ответы

Ответ 1



Особенность TDictionary в том, что когда заканчивается ёмкость под хэши, он увеличивает размер и в этот момент происходит перехэширование всей имеющийся (на данный момент) коллекции. Поэтому если заранее примерно известен размер коллекции, то этот размер умноженный на 2-3 можно поставить в capacity.

Комментариев нет:

Отправить комментарий