Оптимизация сериализации в .NET - Методы оптимизации

ОГЛАВЛЕНИЕ

Методы оптимизации

Мы имеем идентифицированные 'принадлежащие данные', и убедились, что их можно сохранить, используя меньше байтов, чем составляет их реальный размер в памяти, используя метки и известные значения, но можно ли сделать что-то еще для улучшения оптимизации? Безусловно. Давайте посмотрим на пример золотого правила #3 – Не сериализуйте что-либо, если в этом нет необходимости:

Прямой пример этого – Hashtable, используемый внутренне для быстрого нахождения определенного элемента, основанный на одном из его свойств. Этот Hashtable может быть легко восстановлен путем использования десериализованных данных, поэтому нет необходимости сохранять сам Hashtable. Для многих других сценариев проблемой является не сама сериализация, а десериализация. Десериализации нужно знать, что ожидать в потоке данных – если он не неявный, как в предыдущем примере, то вам необходимо каким-то образом сохранять информацию.

Возьмите класс BitVector32: малоизвестный класс, который вам поможет здесь. Смотрите полную информацию в документации, но по существу это структура, занимающая 4 байта, которую можно использовать любым из двух способов (но не двумя одновременно!) – можно использовать ее 32 бита для хранения 32 логических флагов, или можно выделить разделы в несколько битов для размещения в них данных (оптимизация DateTime в SerializationWriter использует этот метод, поэтому посмотрите ее код). В режиме логического флага не имеет смысла определять, которые биты данных были действительно сохранены, и во время десериализации ваш код может проверять флаги, и считывать предполагаемые данные, или выполнять другое действие, где другое действие будет использовать значение по умолчанию, или создавать пустой объект, или ничего не делать (например, значение по умолчанию может быть уже создано в конструкторе).

Другие выгоды от использования BitVector32 в том, что логические значения данных сохраняются как один бит, и BitVector32 может храниться в оптимизированном виде (при условии, что используется не больше 21 бита – в противном случае используйте Write(BitVector32) для фиксированных 4 байтов), чтобы  BitVector32,BitArray, который использует один бит для одного элемента (округляется до ближайшего байта), но может сохранить много битов. использующий меньше 8 флагов, занимал только один байт! Аналогично, если вам нужно использовать много флагов, например, если у вас есть большой список объектов, и вам нужно сохранить логический флаг для каждого, то используйте

Как пример полезности битовых флагов, здесь приводится пример кода из быстрого сериализатора DataSetBitVector32.CreateMask(), который перегружен, чтобы связывать последующие маски с предыдущими. Они статические и доступны только для чтения, поэтому эффективно используют память. Данный набор флагов предназначен для DataColumn: он занимает 2 байта для одного сериализованного столбца, но учтите, что некоторые данные, такие как AllowNull и ReadOnly, уже сериализованы с помощью самого флага, и другие данные сейчас будут сериализованы только при определенных условиях. По сути, один битовый флаг (HasAutoIncrement) используется для сериализации трех элементов данных при определенных условиях (AutoIncrement, AutoIncrementSeed и AutoIncrementStep). serializer, о котором будет написано в части 2: Флаги создаются с помощью использования метода

static readonly int MappingTypeIsNotElement

= BitVector32.CreateMask();
static readonly int AllowNull = BitVector32.CreateMask(MappingTypeIsNotElement);
static readonly int HasAutoIncrement = BitVector32.CreateMask(AllowNull);
static readonly int HasCaption = BitVector32.CreateMask(HasAutoIncrement);
static readonly int HasColumnUri = BitVector32.CreateMask(HasCaption);
static readonly int ColumnHasPrefix = BitVector32.CreateMask(HasColumnUri);
static readonly int HasDefaultValue = BitVector32.CreateMask(ColumnHasPrefix);
static readonly int ColumnIsReadOnly =
                BitVector32.CreateMask(HasDefaultValue);
static readonly int HasMaxLength = BitVector32.CreateMask(ColumnIsReadOnly);
static readonly int DataTypeIsNotString = BitVector32.CreateMask(HasMaxLength);
static readonly int ColumnHasExpression =
                BitVector32.CreateMask(DataTypeIsNotString);
static readonly int ColumnHasExtendedProperties =
                BitVector32.CreateMask(ColumnHasExpression);

static BitVector32 GetColumnFlags(DataColumn dataColumn)
{
  BitVector32 flags = new BitVector32();
  flags[MappingTypeIsNotElement] =
        dataColumn.ColumnMapping != MappingType.Element;
  flags[AllowNull] = dataColumn.AllowDBNull;
  flags[HasAutoIncrement] = dataColumn.AutoIncrement;
  flags[HasCaption] = dataColumn.Caption != dataColumn.ColumnName;
  flags[HasColumnUri] = ColumnUriFieldInfo.GetValue(dataColumn) != null;
  flags[ColumnHasPrefix] = dataColumn.Prefix != string.Empty;
  flags[HasDefaultValue] = dataColumn.DefaultValue != DBNull.Value;
  flags[ColumnIsReadOnly] = dataColumn.ReadOnly;
  flags[HasMaxLength] = dataColumn.MaxLength != -1;
  flags[DataTypeIsNotString] = dataColumn.DataType != typeof(string);
  flags[ColumnHasExpression] = dataColumn.Expression != string.Empty;
  flags[ColumnHasExtendedProperties] =
        dataColumn.ExtendedProperties.Count != 0;
  return flags;
}

Здесь есть методы, которые используют флаги для сериализации/десериализации всех столбцов в DataTable. Вы можете увидеть флаги, используемые для объединения сериализации дополнительных данных с обязательными данными, такими как ColumnName, и данными по умолчанию, такими как DataType, причем эти данные всегда обязательны, но требуют сериализации, если они не выбраны нами по умолчанию (в этом случае typeof(string)).

void SerializeColumns(DataTable table)
{
  DataColumnCollection columns = table.Columns;
  writer.WriteOptimized(columns.Count);

  foreach(DataColumn column in columns)
  {
    BitVector32 flags = GetColumnFlags(column);
    writer.WriteOptimized(flags);

    writer.WriteString(column.ColumnName);
    if (flags[DataTypeIsNotString])
        writer.Write(column.DataType.FullName);
    if (flags[ColumnHasExpression])
        writer.Write(column.Expression);
    if (flags[MappingTypeIsNotElement])
        writer.WriteOptimized((int) MappingType.Element);

    if (flags[HasAutoIncrement]) {
      writer.Write(column.AutoIncrementSeed);
      writer.Write(column.AutoIncrementStep);
    }

    if (flags[HasCaption]) writer.Write(column.Caption);
    if (flags[HasColumnUri])
        writer.Write((string) ColumnUriFieldInfo.GetValue(column));
    if (flags[ColumnHasPrefix]) writer.Write(column.Prefix);
    if (flags[HasDefaultValue]) writer.WriteObject(column.DefaultValue);
    if (flags[HasMaxLength]) writer.WriteOptimized(column.MaxLength);
    if (flags[TableHasExtendedProperties])
        SerializeExtendedProperties(column.ExtendedProperties);
  }
}
void DeserializeColumns(DataTable table)
{
  int count = reader.ReadOptimizedInt32();
  DataColumn[] dataColumns = new DataColumn[count];
  for(int i = 0; i < count; i++)
  {
    DataColumn column = null;
    string columnName;
    Type dataType;
    string expression;
    MappingType mappingType;

    BitVector32 flags = reader.ReadOptimizedBitVector32();
    columnName = reader.ReadString();
    dataType = flags[DataTypeIsNotString] ?
               Type.GetType(reader.ReadString()) :
               typeof(string);
    expression = flags[ColumnHasExpression] ?
                 reader.ReadString() : string.Empty;
    mappingType = flags[MappingTypeIsNotElement] ?
                  (MappingType) reader.ReadOptimizedInt32() :
                  MappingType.Element;

    column = new DataColumn(columnName, dataType,
                            expression, mappingType);
    column.AllowDBNull = flags[AllowNull];
    if (flags[HasAutoIncrement]) {
        column.AutoIncrement = true;
        column.AutoIncrementSeed = reader.ReadInt64();
        column.AutoIncrementStep = reader.ReadInt64();
    }
    if (flags[HasCaption])
        column.Caption = reader.ReadString();
    if (flags[HasColumnUri])
        ColumnUriFieldInfo.SetValue(column, reader.ReadString());
    if (flags[ColumnHasPrefix])
        column.Prefix = reader.ReadString();
    if (flags[HasDefaultValue])
        column.DefaultValue = reader.ReadObject();
    column.ReadOnly = flags[ColumnIsReadOnly];
    if (flags[HasMaxLength])
        column.MaxLength = reader.ReadOptimizedInt32();
    if (flags[TableHasExtendedProperties])
        DeserializeExtendedProperties(column.ExtendedProperties);

    dataColumns[i] = column;
  }
  table.Columns.AddRange(dataColumns);
}

Во второй части  этой статьи будет более подробно описано использование битовых флагов и сериализация дочерних объектов, чтобы полностью использовать преимущества методов оптимизации, перечисленных в этой статье.

Автор: SimmoTech

Загрузить файлы версии v1.0 - 19.2 Kb (только .NET 1.1)

Загрузить файлы версии v2.0 - 39.3 Kb (.NET 1.1 / NET 2.0)

Загрузить файлы версии v2.1 - 50.9 Kb (.NET 1.1 / NET 2.0)