Запуск XmlSerializer ОГРОМНАЯ потеря производительности в 64-битных системах

Я испытываю действительно ОГРОМНУЮ потерю производительности при вызове простого XmlSerializer.Deserizlize() для класса с большим количеством полей.

ПРИМЕЧАНИЕ. Я пишу код дома без Visual Studio, поэтому в нем могут быть ошибки.

Мой сериализуемый класс плоский и имеет сотни полей:

[Serializable]
class Foo
{
    public Foo() { }

    [XmlElement(ElementName = "Field1")]
    public string Field1;

    // [...] 500 Fields defined in the same way

    [XmlElement(ElementName = "Field500")]
    public string Field500;
}

Мое приложение десериализует входную строку (даже маленькую):

 StringReader sr = new StringReader(@"<Foo><Field1>foo</Field1></Foo>");
 XmlSerializer serializer = new XmlSerializer(typeof(Foo));
 object o = serializer.Deserialize(sr);

При запуске приложения в 32-битных системах (или с принудительной 32-битностью с помощью corflags.exe) код занимает около ОДНОЙ СЕКУНДЫ в первый раз (генерация временного класса сериализации и все такое...), затем он близок к 0.

При запуске приложения в 64-битных системах код занимает ОДНУ МИНУТУ в первый раз, а затем близок к 0.

Что могло привести к зависанию системы на такое долгое время во время первого выполнения XmlSerializer для большого класса в 64-битной системе?

Прямо сейчас я не уверен, должен ли я винить создание/удаление временного класса, инициализацию таблицы имен xml, CAS, поиск Windows, антивирус или Санта-Клауса...

СПОЙЛЕРЫ

Вот мои тесты, не читайте это, если не хотите отвлекаться на мои (возможные) ошибки анализа.

  • Запуск кода из отладчика Visual Studio заставляет код работать БЫСТРО даже в 64-битных системах.
  • Добавление (полностью недокументированного) переключателя system.diagnostic «XmlSerialization.Compile», который не позволяет системе удалять временные классы сериализации, заставляет код работать БЫСТРО даже в 64-битных системах.
  • Взяв временный класс FooXmlSerializer, созданный средой выполнения, включая .cs в моем проекте, и используя его вместо XmlSerializer, код выполняется БЫСТРО даже в 64-разрядных системах.
  • Создание того же класса FooXmlSerializer с sgen.exe, включая .cs в моем проекте, и использование его вместо XmlSerializer позволяет выполнять код БЫСТРО даже в 64-разрядных системах.
  • Создание того же класса FooXmlSerializer с sgen.exe, ссылка на сборку Foo.XmlSerializers.dll в моем проекте и использование ее вместо XmlSerializer заставляет код работать МЕДЛЕННО даже в 64-битных системах (это меня сильно раздражает< /сильный>)
  • Потеря производительности происходит только в том случае, если входные данные для десериализации действительно содержат поле большого класса (это меня тоже сильно беспокоит)

Чтобы дополнительно объяснить последний пункт, если у меня есть класс:

[Serializable]
class Bar
{
    public Bar() { }

    [XmlElement(ElementName = "Foo")]
    public Foo Foo; // my class with 500 fields
}

Десериализация выполняется медленно только при передаче дочернего элемента Foo. Даже если я уже выполнил десериализацию:

 StringReader sr = new StringReader(@"<Bar></Bar>");
 XmlSerializer serializer = new XmlSerializer(typeof(Bar));
 object o = serializer.Deserialize(sr); // FAST

 StringReader sr = new StringReader(@"<Bar><Foo><Field1>foo</Field1></Foo></Bar>");
 XmlSerializer serializer = new XmlSerializer(typeof(Bar));
 object o = serializer.Deserialize(sr); // SLOW

EDIT Я забыл сказать, что проанализировал выполнение с помощью Process Monitor и не вижу, чтобы какая-либо задача занимала много времени в моем приложении или в csc.exe, или в чем-либо, связанном с Framework. Система просто делает другие вещи (или я что-то упускаю), такие как антивирус, explorer.exe, индексация поиска Windows (уже пытался их отключить)


person Filini    schedule 09.11.2010    source источник
comment
что не так с сгеном? почему бы тебе просто не пойти по этой дороге?   -  person Tim Mahy    schedule 09.11.2010
comment
мой класс Foo() и код, вызывающий XmlDeserializer, динамически генерируются старой системой, и я не могу проводить слишком много рефакторинга. У меня уже есть приемлемое решение (используя настройку переключателя system.diagnostic), но мне ОЧЕНЬ хотелось бы знать, что зависает в системе :)   -  person Filini    schedule 09.11.2010
comment
Значит, ваша система на самом деле не занята, она просто сидит там? Это предполагает какой-то тайм-аут, возможно, попытку разрешить схему XML или что-то в этом роде. Однако я ожидаю одинаковой задержки как в 32-, так и в 64-разрядных средах. Разве что схема каким-то образом ранее кэшировалась в 32-битном режиме. Р е а ч е с т в о . . .   -  person TMN    schedule 09.11.2010
comment
класс Foo не может быть десериализован, поскольку он является внутренним, а не общедоступным (в вашем примере). В моей системе сборка 32-разрядного выпуска заняла 0,055 секунды для десериализации. Как и в случае с вашими результатами, мой 64-битный релиз занял 12,5 секунды! Так что там примерно 227-кратное замедление.   -  person Jesse C. Slicer    schedule 09.11.2010
comment
Джесси, мой класс Foo общедоступен в моем реальном коде. Я писал это дома без Visual Studio, не все написал...   -  person Filini    schedule 09.11.2010
comment
и если вы добавите больше полей в свой класс, вы увидите, как растет время. затем добавьте переключатель XmlSerialization.Compile и посмотрите, как он работает быстро :)   -  person Filini    schedule 09.11.2010
comment
Что я могу сказать по XmlSerialization.Compile, так это то, что он включает всю отладочную информацию и отключает оптимизацию. Таким образом, вы запускаете отладочную версию.   -  person Jesse C. Slicer    schedule 10.11.2010
comment
+1 в надежде, что этот вопрос получит профессиональное внимание. Интересная ситуация.   -  person John K    schedule 10.11.2010
comment
Из того, что я могу сказать из XmlSerialization.Compile, он позволяет избежать удаления временных файлов сериализации. однако он принимает логические значения, а также 1, 2, 3, 4, так что я могу ошибаться   -  person Filini    schedule 10.11.2010
comment
Или это может каким-то образом заставить использовать 32-битный JIT-компилятор (если это связано с отладкой)... если бы только это было задокументировано :)   -  person Filini    schedule 10.11.2010


Ответы (4)


Я не знаю, связано ли это вообще, но у меня была проблема с XSLT, и я нашел high-on-x64-for-xslcompiledtransform-transform-then-on-x86" rel="noreferrer">те довольно интересные комментарии Microsoft о 64-битном JITter:

Корень проблемы связан с двумя вещами. Во-первых, у x64 JIT-компилятора есть несколько алгоритмов квадратичного масштабирования. К сожалению, один из них — генератор отладочной информации. Так что для очень больших методов это действительно выходит из-под контроля.

[...]

некоторые алгоритмы в 64-битном JIT с полиномиальным масштабированием. На самом деле мы работаем над переносом 32-битного JIT-компилятора на x64, но он не увидит свет до следующего параллельного выпуска среды выполнения (как в "2.0 & 4.0 выполнялись параллельно, но 3.0/3.5/3.5SP1 были выпусками «на месте». Убедитесь, что это исправлено, когда только что перенесенный JIT будет готов к отправке.

Опять же, речь идет о совершенно другом вопросе, но мне кажется, что комментарии 64-Bit JITter универсальны.

person Community    schedule 10.11.2010
comment
вы правы, это другая проблема, но проблемы с производительностью 64-битного JITter могут быть связаны - person Filini; 10.11.2010
comment
Во-вторых, JIT несколько ответственен за это, я не знаю почему, но это так! - person massimogentilini; 10.11.2010

ОБНОВЛЕНИЕ:

Мне удалось это воспроизвести, исследование показывает, что больше всего времени было потрачено на JIT-компилятор:

JittingStarted: "Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderFoo", "Read2_Foo", "экземпляр класса SerializersTester.Foo"

Вы можете легко доказать это без какого-либо инструмента профилирования.

  • Создайте *.XmlSerializers.dll через sgen для целей x86 и x64.
  • Создание нативных образов через ngen.

Вы можете заметить, что генерация x64 будет намного медленнее по сравнению со сборкой x86.

Точная причина скрывается в x64 JIT-внутренностях (кстати, она полностью отличается от x86), и, к сожалению, у меня нет достаточно свободного времени, чтобы найти ее.

Чтобы избежать такой потери производительности, вы можете сгенерировать сборку сериализатора с помощью sgen, сослаться на нее и скомпилировать в собственный образ с помощью ngen во время установки приложения на ПК конечного пользователя.

person Nick Martyshchenko    schedule 09.11.2010
comment
Случается как с .NET 2.0, 3.5, так и с 4.0. Сборка Generate сериализации создает классы сериализации только для ссылок на веб-службы. Я использовал sgen.exe вручную, см. мой пост. Профайлером пока не пользовался. - person Filini; 09.11.2010
comment
Точная причина скрывается в x64 JIT-внутренностях. Надеюсь, тема привлечет члена команды JIT, который сможет пролить свет на это. - person Jesse C. Slicer; 10.11.2010
comment
Ник, я до сих пор не понимаю, почему класс Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderFoo, на который ссылается внешняя сборка, работает медленно, а добавляется в проект быстро. Дает ли наличие класса foo и его класса сериализатора в одной сборке преимущество для JIT-компилятора? - person Filini; 10.11.2010
comment
@Filini, я не могу дать вам точный ответ, не углубившись в внутренности 64-битного джиттера, здесь я могу только догадываться. Возможно, вам следует опубликовать этот вопрос на connect.microsoft.com. Команда компилятора x64 JIT знает гораздо больше подробностей о том, как это работает. - person Nick Martyshchenko; 10.11.2010
comment
+1 за создание сборки сериализатора через sgen, ссылку на нее и компиляцию в собственный образ через ngen. Это хорошее временное исправление, но мы все еще надеемся, что член команды JIT увидит это. - person Jesse C. Slicer; 19.11.2010

Чтобы уточнить "XmlSerialization.compile", вот что это происходит:

Если мы запустим код без файла .config на 64-битной системе, он будет медленным.

Если мы добавим следующий раздел в файл .config для приложения

<configuration>
   <system.diagnostics>
     <switches>
        <add name="XmlSerialization.Compilation" value="4"/>
     </switches>
   </system.diagnostics>
</configuration>

Результат следующий:

  • Файл .cs, DLL и файл PDB для сериализатора остаются во временной папке.
  • сериализатор запускается быстро, он все еще медленнее, чем на 32-битном, но вполне приемлем (1-2 секунды вместо 60)

Возможно, создание DLL в режиме отладки (поскольку доступны файлы PDB) изменит поведение JIT-компилятора, сделав его снова быстрым...

person massimogentilini    schedule 10.11.2010

Microsoft знала об этом с момента выпуска 64-битной версии .NET:

http://connect.microsoft.com/VisualStudio/feedback/details/508748/memory-consumption-alot-higher-on-x64-for-xslcompiledtransform-transform-then-on-x86

Из MSFT: «Компилятор x64 JIT имеет несколько алгоритмов с квадратичным масштабированием. ... это было то, что мы видели несколько раз с тех пор, как 64-битная платформа впервые была выпущена в 2005 году». и

"Эта проблема а) известна и б) не очень тривиальна для решения. Это проблема дизайна 64-битной JIT. Мы находимся на ранних стадиях замены нашей 64-битной реализации JIT, поэтому она в конечном итоге будет получить адрес, но, к сожалению, не в срок CLR 4.0."

person Barka    schedule 21.02.2013