Масштабируемость сборщика мусора .NET 4 - PullRequest
14 голосов
/ 16 августа 2010

Я недавно проверил сборщик мусора в .NET 4, интенсивно выделяя его из нескольких потоков.Когда выделенные значения были записаны в массив, я не наблюдал масштабируемости, как я ожидал (потому что система борется за синхронизированный доступ к общему старому поколению).Тем не менее, когда выделенные значения были немедленно отброшены, я был в ужасе, что также не наблюдал масштабируемости!

Я ожидал, что временный случай будет масштабироваться почти линейно, потому что каждый поток должен просто очистить детскую gen0 и начать заново безсостязание за любые общие ресурсы (ничего не сохранилось для более старых поколений, и нет кеша L2, потому что gen0 легко помещается в кеш L1).

Например, эта статья MSDN гласит :

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

Может ли кто-нибудь проверить мои выводы и / или объяснить это расхождение между моими предсказаниями и наблюдениями?

Ответы [ 5 ]

12 голосов
/ 16 августа 2010

Не совсем уверен, о чем идет речь и точно , что вы видели на своей машине. Однако на вашем компьютере есть две разные версии CLR. Mscorwks.dll и mscorsvc.dll. Первый - тот, который вы получаете, когда запускаете программу на рабочей станции, второй - на одной из серверных версий Windows (например, Windows 2003 или 2008).

Версия рабочей станции удобна для вашего локального ПК, она не поглощает все ресурсы машины. Вы все еще можете прочитать свою электронную почту, пока GC продолжается. Версия сервера оптимизирована для масштабирования на аппаратном уровне сервера. Много ОЗУ (GC не запускает так быстро) и много ядер ЦП (мусор собирается на нескольких ядрах). Ваша цитируемая статья, вероятно, говорит о версии сервера.

Вы можете выбрать версию сервера на своей рабочей станции, используйте элемент <gcServer> в вашем файле .config.

11 голосов
/ 23 августа 2010

Не полный ответ на вопрос, а просто чтобы прояснить некоторые заблуждения: .NET GC работает только в режиме рабочей станции.В режиме сервера он использует параллельный GC Stop-the-World.Подробнее здесь .Отдельные питомники в .NET предназначены прежде всего для того, чтобы избежать синхронизации при распределении;тем не менее они являются частью глобальной кучи и не могут быть собраны отдельно.

4 голосов
/ 13 октября 2010

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

Также следует помнить, что «куча» с двойными указателями состоит из 2 частей - блокапамять и таблица указателей (чтобы можно было перемещать блоки, но ваш код всегда имеет один адрес).Такая таблица является критически важным, но очень экономичным ресурсом на уровне процесса, и почти единственный способ подчеркнуть это - заполнить ее большими быстрыми выпусками - так что вам удалось это сделать: -))

В целом правилоGC - утечка :-) Не навсегда, конечно, но вроде как можно дольше.Если вы помните, как люди ходят, говоря "не принуждайте коллекции GC"?Это часть истории.Кроме того, коллекция «останови мир» на самом деле гораздо более эффективна, чем «параллельная», и раньше ее называли более удачным названием «кража циклов» или «сотрудничество шедулеров».Только фаза метки должна заморозить планировщик, и на сервере это происходит из нескольких потоков (все равно N ядер простаивают :-) Единственная причина для другого состоит в том, что он может выполнять операции в реальном времени, такие как воспроизведение видео, нервноточно так же, как это делает более длинный квант потока.

И снова, если вы конкурируете с инфраструктурой на коротких и частых пакетах ЦП (малое распределение, почти не работает, быстрый выпуск), единственное, что вы увидите / измерите, будетбыть шумом GC и JIT.

Если бы это было что-то реальное, то есть не просто эксперименты, лучшее, что вы можете сделать, это использовать массивы больших значений в стеке (структуры).Они не могут быть принудительно загружены в кучу и являются настолько локальными, насколько могут получить локальные, и не подвержены никакому бэкдор-движению => кеш должен их любить :-) Это может означать переключение в «небезопасный» режим с использованием обычных указателей и, возможно,делать немного ассигнования самостоятельно (если вам нужно что-то простое, например, списки), но это небольшая цена за то, чтобы выкинуть GC :-) Попытка принудительно поместить данные в кеш также зависит от сохранения стеков в другом случае - помните, что выне одиноки.Также может помочь вашим потокам некоторая работа, которая стоит как минимум несколько квантов между выпусками.Наихудший сценарий будет, если вы выделите и освободите в пределах кванта сигнала.

4 голосов
/ 16 августа 2010

Я могу рискнуть парой предположений относительно того, что происходит.

(1) Если у вас один поток, и в поколении 0 свободно M пространство, тогда GC будет работать только один раз по M байтовбыли выделены.

(2) Если у вас есть N потоков, и GC делит поколение 0 на N / M пространства на поток, GC будет работать каждый раз, когда поток выделяет N / M байтов.Здесь показано, что GC необходимо «остановить мир» (т.е. приостановить все запущенные потоки), чтобы отметить ссылки из корневых наборов потоков.Это не дешево.Таким образом, не только GC будет работать чаще, он будет выполнять больше работы с каждой коллекцией.

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

Я не думаю, что это проблема .NET GC, скорее это проблема GC в целом.Однажды коллега выполнил простой тест «пинг-понг», отправляя простые целочисленные сообщения между двумя потоками, используя SOAP.Тест выполнялся вдвое быстрее, когда два потока были в отдельных процессах, потому что распределение памяти и управление были полностью отделены!

3 голосов
/ 16 августа 2010

или объясните это расхождение между моими предсказаниями и наблюдениями?

Сравнительный анализ труден.
Тестирование подсистемы, которая не находится под вашим полным контролем, еще сложнее.

...