Как улучшить производительность сборщика мусора .NET 4.0 в высококонкурентном коде? - PullRequest
12 голосов
/ 22 февраля 2010

Я использую библиотеку параллельных задач из .NET Framework 4 (в частности, Parallel.For и Parallel.ForEach), однако я получаю чрезвычайно посредственные ускорения при распараллеливании некоторых задач, которые выглядят так, как будто их легко распараллелить на двойной Основная машина.

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

Например, есть некоторые методы, которые могут быть полезны в этой ситуации:

  • Стоит ли пытаться управлять ГХ вручную?
  • Должен ли я использовать Dispose?
  • Должен ли я прикреплять объекты?
  • Должен ли я делать другие опасные трюки с кодом?

Постскриптум:

Проблема не в том, что GC запускается слишком часто, а в том, что GC препятствует эффективной параллельной работе параллельного кода. Я также не считаю «распределять меньше объектов» приемлемым ответом. Для этого требуется переписать слишком много кода, чтобы обойти плохо распараллеленный сборщик мусора.

Я уже нашел один трюк, который помогал повысить общую производительность ( с использованием gcServer ), но не помогал одновременной производительности. Другими словами, Parallel.For был только на 20% быстрее, чем последовательный цикл For, для смущающе параллельной задачи.

POST-Постскриптум:

Хорошо, позвольте мне объяснить подробнее, у меня довольно большая и сложная программа: оптимизирующий интерпретатор. Это достаточно быстро, но я хочу, чтобы его производительность выполнялась, когда выполняемые параллельные задачи (примитивные операции встроены в мой язык) хорошо масштабируются и доступно больше ядер. Я выделяю много мелких объектов во время оценки. Весь дизайн интерпретатора основан на том, что все значения получены из одного полиморфного базового объекта. Это прекрасно работает в однопоточном приложении, но когда мы пытаемся применить Task Parallel Library для параллельных вычислений, это не дает никаких преимуществ.

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

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

Ответы [ 8 ]

5 голосов
/ 22 февраля 2010

Если GC работает слишком часто из-за слишком большого количества объектов, выделенных / GC-ed, попробуйте выделить меньше из них :)

В зависимости от вашего сценария - попробуйте повторно использовать существующие объекты, создать пул объектов, использовать «более легкие» объекты, которые не оказывают слишком большого давления в памяти (или больше для уменьшения количества выделенных объектов).

Не пытайтесь "управлять GC", вызывая GC. Соберите явно, это очень редко окупается ( Рико Мариани так говорит )

или http://blogs.msdn.com/ricom/archive/2003/12/02/40780.aspx

2 голосов
/ 23 февраля 2010

У меня есть идея - почему бы не попробовать альтернативную реализацию GC? .NET предоставляет три.

http://blogs.msdn.com/maoni/archive/2004/09/25/234273.aspx

Исходя из вашего описания проблемы, мне было бы любопытно посмотреть, как сервер GC работает для вас, поскольку он обеспечивает отдельную кучу для каждого ядра. Вероятно, стоит также посмотреть на режим Background GC, который добавляет .NET 4.

http://blogs.msdn.com/maoni/archive/2008/11/19/so-what-s-new-in-the-clr-4-0-gc.aspx

Надеюсь, это немного более полезно для вашего конкретного случая, чем ответы на данный момент.

2 голосов
/ 22 февраля 2010

За ваши четыре очка:

  1. См. Как повысить производительность сборщика мусора .NET 4.0 в высококонкурентном коде? (1)
  2. Вы должны утилизировать, если ваши объекты содержат ресурсы, особенно ресурсы для неуправляемых объектов. Распоряжение исполняется немедленно. Возможный финализатор (~ Destructor в C ++) вызывается только при запуске GC и удалении объекта из памяти.
  3. Закрепление объектов имеет смысл, только если объект передается в неуправляемый фрагмент кода, например неуправляемый c ++ dll. В противном случае оставьте сборщик мусора, чтобы внести свой вклад в поддержание чистоты памяти. Закрепление также может привести к фрагментации памяти.
  4. Нет, если не нужно.

Одна вещь, о которой стоит подумать, это переместить распределение из ваших циклов - если это возможно. Во многих случаях, когда вы можете сделать это, он также позволяет вам повторно использовать уже выделенные объекты, что обеспечивает дополнительную производительность (по крайней мере, как показывает мой опыт) (см. Также Как улучшить производительность сборщика мусора .NET 4.0 в высококонкурентный код? ).

Степень параллельного выполнения всегда зависит от задачи, которую вы выполняете, в случае вычисления максимально достижимый параллелизм составляет

2 голосов
/ 22 февраля 2010

1) Вы не можете и не должны управлять ГХ вручную.

2) Утилизация - это всего лишь указание для GC, оно в любом случае пройдет, когда он почувствует себя хорошо. : P

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

РЕДАКТИРОВАТЬ: Всякий раз, когда ГХ работает, ВСЕ потоки должны переходить в состояние сна, чтобы позволить ему выполнять свою работу. Это причина замедления, если коллекций много, как в вашем случае. Нет другого способа справиться с этим, кроме как уменьшить генерацию новых объектов.

1 голос
/ 09 апреля 2011

что именно делает GC, что может привести к плохой работе сильно параллельного кода при большом количестве выделений

.NET GC, вероятно, сериализует копирование и сбор выделенных объектов. .NET GC - это стандартный сборщик поколений, который разделяет детскую (gen0) на отдельные арены для отдельных ядер / потоков, чтобы справиться с некоторым параллелизмом. Но сбор всех данных, выделенных со всех ядер, по-видимому, производится последовательно.

Однако я не уверен, что в данном случае GC является источником вашей проблемы. Есть много способов добиться плохой масштабируемости на многоядерном компьютере. Неспособность воспользоваться преимуществами кэшей является еще одним распространенным явлением, и это приводит к тому, что все ядра останавливаются при доступе к разделяемой памяти, что почти невозможно обнаружить масштабируемостью ...

1 голос
/ 22 февраля 2010

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

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

  • Реализовать лучший GC или
  • Дайте ГК меньше работы

Первый пункт практически невозможен. Для замены .NET GC потребовалось бы много взлома, и потребовалось бы много работы, чтобы спроектировать GC, даже удаленно такой же эффективный, как .NET.

Второй пункт - действительно единственный вариант: если сборка мусора требует синхронизации, убедитесь, что происходит меньше сборок. Обычно они возникают, когда куча gen0 слишком заполнена, чтобы удовлетворить запрос на выделение.

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

  1. Использование (распределенных по стеку) структур вместо классов может помочь снизить давление GC. Особенно маленькие, недолговечные объекты, вероятно, выиграют от преобразования в структуры,
  2. Повторно используйте выделенные вами объекты. Долгоживущие объекты перемещаются в большие кучи, где коллекции происходят редко. Например, переместите выделения из циклов.
1 голос
/ 22 февраля 2010

Это факт жизни. Почти все схемы управления памятью сериализуют код, который до некоторой степени выглядит смущающе параллельно. Я думаю, что C # имеет локальные распределители потоков, поэтому он должен сериализоваться только для коллекций. Тем не менее, я бы порекомендовал объединить / повторно использовать ваши наиболее часто выделяемые объекты и массивы и, возможно, преобразовать некоторые небольшие неполиморфные объекты в структуры и посмотреть, поможет ли это.

0 голосов
/ 22 февраля 2010

Параллельные задачи и даже необработанные потоки не являются волшебными пулями для ускорения работы вашего кода. Если у вас есть какие-либо блокировки, ресурсы или только несколько ядер, вы можете замедлить код, пытаясь быть многопоточным. Вы также должны убедиться, что у вас нет обменов контекста, и, надеюсь, у вас более 4 ядер. (Не забудьте, что GC, CLR, Windows, а также другие приложения и службы борются за ресурсы / циклы.)

Вы также должны знать, что закрепление и небезопасный код могут замедлить некоторые действия. Им требуются специальные операции как от CLR, так и от GC, чтобы обеспечить безопасность памяти и ресурсов (например, GC также не может сжиматься, если вы pin или если вы небезопасны .)

Библиотека параллельных задач была создана для общего пользования. Если вам нужен высокооптимизированный код, вам также может понадобиться управлять своими собственными потоками. (В отличие от многих блогов говорят, что в этой профессии нет волшебных пуль).

Лучше всего будет создать экземпляр вашего рабочего класса для потока, чтобы избежать создания и деконструкции для каждого действия. Проверьте ThreadStaticAttribute . Насколько я понимаю, в .Net 4.0 есть и другие варианты, но у меня еще не было возможности поработать с ними.

...