Использование памяти Protobuf.net

Привет. Давний любитель protobuf.net.

Хотя быстрый вопрос. У меня есть многопоточное приложение C #, которое десериализует около 100 объектов в секунду, что составляет около 50 МБ / с. Я наблюдаю очень большое использование памяти, намного превышающее ту, которую я десериализирую. Я запустил приложение через «Профилировщик памяти Red Gate ANTS», и он показывает мне огромное количество объектов памяти поколения 2 из-за protobuf (более 50% использования приложения). Большинство объектов являются значениями типа int и связаны с:

- TypeModel.TryDeserializeList()
- ProtoBuf.Meta.BasicList

Любая помощь в сокращении использования памяти второго поколения будет оценена по достоинству.

Марк


person MarcF    schedule 09.12.2011    source источник
comment
Интересно - можете ли вы примерно дать представление о том, как выглядит модель? Это массив целых чисел? Список int? Также: что это за структура? Полный .NET? CF? SL? Вероятно, можно легко решить эту проблему, но, чтобы быть уверенным, нужно больше контекста.   -  person Marc Gravell    schedule 09.12.2011
comment
Также - является ли корневой объект списком? В принципе ... Может haz codez? (или, по крайней мере, что-то подобное)   -  person Marc Gravell    schedule 09.12.2011
comment
Ok. Приносим извинения за отсутствие подробностей. Что вы имеете в виду под тем, как выглядит модель? Самый большой объект, который я десериализирую, - это массив int [33554432] с использованием .net 4.0.   -  person MarcF    schedule 11.12.2011
comment
К сожалению, я не могу предложить конкретного фрагмента кода, который, как я знаю, является причиной проблемы. Вся информация, которую мне дает профилировщик памяти, заключается в том, что существует большое количество значений in как в Gen 2, так и в куче больших объектов, так или иначе связанных с ProtoBuf.Meta.BasicList. Также стоит добавить, что когда вся десериализация завершена и я вызываю ручную сборку мусора, использование памяти приложением падает до 20% от того, что было при десериализации. Это просто ожидаемое использование памяти protobuf при десериализации такого большого массива int?   -  person MarcF    schedule 11.12.2011
comment
Главное, что я хочу знать, это ... Что за буква T используется в Deserialize<T> - это Deserialize<int[]>?   -  person Marc Gravell    schedule 11.12.2011


Ответы (2)


Мне кажется, что корень T здесь - это сам массив, т.е.

int[] values = Serializer.Deserialize<int[]>(source);

Если это так, то в настоящее время он использует несколько неоптимальный путь для этого сценария (по причине: использования того же пути кода даже на платформах с слабые модели метапрограммирования / отражения, такие как iOS). Я постараюсь потратить несколько часов на то, чтобы убрать это в какой-то момент, но, отвечая на ваш вопрос, вы сможете избежать проблемы здесь, просто добавив родительский объект:

[ProtoContract]
public class MyDataWrapper { // need a new name...
    [ProtoMember(1)]
    public int[] Values { get;set; }
}

а потом:

int[] values = Serializer.Deserialize<MyDataWrapper>(source).Values;

Это фактически полностью совместимо с данными, уже сериализованными через Serialize<int[]>, если используется номер поля 1. Еще одно преимущество этого подхода состоит в том, что при желании вы можете использовать «упакованный» подформат (доступен только для списков / массивов примитивов, таких как int); хотя, возможно, в данном случае это не самая лучшая идея из-за большой длины (может потребоваться буферизация при сериализации).


Дополнительный контекст; «v1» здесь в основном использует MakeGenericType для переключения на что-то подобное на лету; однако, поскольку этот подход недоступен на многих дополнительных платформах, на которые нацелена "v2", здесь используется менее элегантный подход. Но теперь, когда он там довольно стабилен, я мог бы повторно добавить оптимизированную версию при работе на полной версии .NET 2.0 или выше.

person Marc Gravell    schedule 11.12.2011
comment
Да, это была проблема. Добавление оболочки и ничего больше полностью устранило использование большого объема памяти. Еще раз большое спасибо, Марк. - person MarcF; 11.12.2011
comment
@MarcF k; Я посмотрю, смогу ли я повторно добавить оптимизацию для будущего использования - person Marc Gravell; 11.12.2011
comment
@MarcGravell Я знаю, что это старый вопрос, но я столкнулся с той же проблемой GC. Не могли бы вы немного рассказать об обходном пути (используя класс-оболочку): будет ли это работать для всех типов массивов (например, ComplexType [] в качестве корневого объекта, где ComplexType может быть смоделирован с использованием наследования)? Являются ли обернутое решение и решение для массива полностью эквивалентными (т. Е. Создает ли protobuf просто класс-оболочку при сериализации массивов)? И, наконец, искали ли вы решение проблемы в версии 2.1.0 (предварительная версия)? - person Mads Ravn; 18.12.2015
comment
да, тогда он будет использовать другой путь кода (хотя данные будут идентичными); нет, капитальный ремонт не производился; Я не могу припомнить, сильно ли это повлияет на сборку мусора, но попробовать стоит. Для внешнего типа: нет, это не просто внутренняя оболочка - person Marc Gravell; 18.12.2015
comment
@MarcGravell Спасибо за быстрый ответ. Я добавил результаты тестов в ответ ниже. Эффект довольно заметен, поэтому имеет смысл специально разобрать код для соответствующих платформ. - person Mads Ravn; 18.12.2015

Чтобы уточнить ответ Маркса, я провел быстрый тест

  • Сериализация / десериализация с использованием оболочки против использования массива.
  • С / без сервера GC включен

Тест создал 100000 сложных объектов (1 временной интервал, 2 двойных, 2 целых, 2 целых числа, список строк с от 0 до 4 коротких элементов (1 символ)) и повторил процесс сериализации / десериализации 30 раз и измерил общее время. взято и количество сборов мусора, которые произошли во время выполнения. Результаты были (запущены в выпуске за пределами VS)

GC IsServer: False, GC latency: Interactive, GC LOH compaction: Default
Wrapper serialization
Generation 0: 0 collects
Generation 1: 0 collects
Generation 2: 0 collects
Time: 20.363 s
------------------------
Array serialization
Generation 0: 0 collects
Generation 1: 0 collects
Generation 2: 0 collects
Time: 30.433 s
------------------------
Wrapper deserialization
Generation 0: 109 collects
Generation 1: 47 collects
Generation 2: 16 collects
Time: 71.277 s
------------------------
Array deserialization
Generation 0: 129 collects
Generation 1: 57 collects
Generation 2: 19 collects
Time: 89.145 s


GC IsServer: True, GC latency: Interactive, GC LOH compaction: Default
Wrapper serialization
Generation 0: 0 collects
Generation 1: 0 collects
Generation 2: 0 collects
Time: 20.430 s
------------------------
Array serialization
Generation 0: 0 collects
Generation 1: 0 collects
Generation 2: 0 collects
Time: 30.364 s
------------------------
Wrapper deserialization
Generation 0: 4 collects
Generation 1: 3 collects
Generation 2: 2 collects
Time: 39.452 s
------------------------
Array deserialization
Generation 0: 3 collects
Generation 1: 3 collects
Generation 2: 3 collects
Time: 47.546 s

Итак, мой вывод

  • Подход обертки приносит пользу как сериализации, так и десериализации (причем последняя имеет более выраженный эффект).
  • Накладные расходы на сбор сборщика мусора, вызванные подходом к массиву, более заметны при работе без сборщика мусора сервера. Также обратите внимание, что влияние на производительность сборщика мусора очень велико, если сборщик мусора не запущен и не выполняется десериализация в нескольких потоках (результаты не включены).

Надеюсь, кто-то сочтет это полезным.

(к сожалению, код теста зависит от внутреннего кода, поэтому я не могу опубликовать здесь полный код).

person Mads Ravn    schedule 18.12.2015