2010-08-15 4 views
13

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

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

Например, this MSDN article says:

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

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

+3

Определите, что вы подразумеваете под «без масштабируемости». –

+7

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

+2

Я предполагаю, что здесь, но я, возможно, Джон Харроп выполнял свой тест на N-ядерном компьютере и делал свой тест с n = 1 до N потоков. Тогда масштабирование - это то, как скорость теста изменяется с n. –

ответ

11

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

+1

«они все же являются частью глобальной кучи и не могут быть собраны отдельно». Это именно то, что мне нужно было знать. Спасибо! –

3

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

Бенчмаркинг - это сложно.
Бенчмаркинг подсистемы, которая не под вашим полным контролем, еще сложнее.

12

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

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

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

4

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

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

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

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

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

+0

@Rafe: «GC должен остановить мир». Ты уверен? Я могу представить проекты, в которых все корни для объектов в генерации детского сада находятся в глобальных переменных (один) поточно-локальный стек и запоминаемый набор, созданный барьером записи. –

+1

@Jon: немного поздно, поэтому я могу быть здесь не на месте, но не требовал бы, чтобы каждый регистратор машины поддерживался глобальным или стекным слотом, скорее отрицая много оптимизаций генерации кода? Кроме того, писать барьеры не дешево. Я имею в виду следующее: GC не знает, что нить i не передала ссылку на локальный объект на поток j, поэтому ему нужно проверить корни j, чтобы искать ссылки в ящике i. В любом случае, мое чтение связанной статьи в разделе «Производительность для многопоточных приложений» состояло в том, что .NET GC является типом stop-the-world. – Rafe

+0

@Rafe: «не требовало бы, чтобы каждый регистратор компьютера поддерживался глобальным или стекным слотом, скорее отрицая много оптимизаций генерации кода». Нет, я использовал технику в HLVM, и она генерирует очень быстрый код. –

4

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

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

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

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

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

Смежные вопросы