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

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

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

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

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

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

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

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

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

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

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

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

8 ответов

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

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

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

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


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

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

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

ИЗМЕНИТЬ: Всякий раз, когда GC работает, все потоки должны перейти в состояние ожидания, чтобы позволить ему выполнять свою работу. Это причина замедления, если коллекции много, как в вашем случае. Нет другого способа справиться с этим, кроме как уменьшить генерации новых объектов.


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

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

Основываясь на описании проблемы, мне было бы интересно узнать, как работает GC 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

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


Для четырех пунктов:

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

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

Степень параллельного выполнения всегда зависит от задачи, которую вы выполняете, в случае вычисления максимально достижимый parallelism равен < n раз, где n - количество процессоров - чистое вычисление. В случае операций ввода или вывода n обычно будет превышено.


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

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

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


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


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

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

  • Внедрить лучший GC или
  • Предоставьте GC меньше работы для выполнения

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

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

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

  • Использование (с разбивкой по стеклам) структур вместо классов может помочь уменьшить давление ГК. Особенно небольшие, недолговечные объекты, вероятно, выиграют от преобразования в структуры,
  • Повторно используйте объекты, которые вы выделяете. Долгоживущие объекты перемещаются в большие кучи, где коллекции редко происходят. Например, переместите выделение из циклов.


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

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

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

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

licensed under cc by-sa 3.0 with attribution.