Когда стоит форсировать сборку мусора?


135

Итак, я читал вопрос о том, как заставить сборщик мусора в C # запускать, когда почти все ответы одинаковы: вы можете сделать это, но не должны - за исключением некоторых очень редких случаев . К сожалению, никто не уточняет, что такое такие случаи.

Можете ли вы сказать мне, по какому сценарию на самом деле хорошая или разумная идея форсировать сборку мусора?

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


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

4
@Doval Если вы находитесь в режиме реального времени, и GC не предоставляет гарантии соответствия, вы находитесь между молотом и наковальней. Это может уменьшить нежелательные паузы по сравнению с бездействием, но из того, что я слышал, «легче» избежать распределения в обычном режиме работы.

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

4
Я не могу понять, как вы можете ответить на этот вопрос не для ВМ. Актуален для 32-битных процессов, не актуален для 64-битных процессов. .NET JVM и для высокого класса
Rwong

3
@DavidConrad вы можете заставить его в C #. Отсюда и вопрос.
Омега

Ответы:


127

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

Вы должны знать поведение GC довольно близко, чтобы сделать это с любой логикой или причиной.

Единственный совет по сбору, который я могу дать: никогда не делай этого.

Если вы действительно знаете сложные детали GC, вам не понадобится мой совет, так что это не будет иметь значения. Если вы еще не уверены со 100% уверенностью, что это поможет, вам придется поискать в Интернете и найти ответ, подобный следующему: вам не следует звонить в GC.Collect или, наоборот: вам следует узнать подробности работы GC. внутри и снаружи, и только тогда вы узнаете ответ .

Есть одно безопасное место, в котором есть смысл использовать GC.Collect :

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

Такого рода профилирование - единственный раз, когда я советую собирать вручную кому-либо.


Во всяком случае, надуманный пример

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

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


Итак, давайте поговорим о некоторых семантиках и механизмах в .NET GC ... или ...

Все, что я думаю, я знаю о .NET GC

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

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


GC Concepts

.NET GC возникает в противоречивые моменты, поэтому он называется «недетерминированным», это означает, что вы не можете рассчитывать на то, что он произойдет в определенное время. Это также сборщик мусора поколений, что означает, что он разбивает ваши объекты на количество пройденных GC-проходов.

Объекты в куче Gen 0 прошли через 0 коллекций, они были созданы недавно, так как с момента их создания коллекция не возникла. Объекты в вашей куче Gen 1 пережили один проход сбора, и объекты в вашей куче Gen 2 пережили 2 прохода сбора.

Теперь стоит отметить причину, по которой он квалифицирует эти конкретные поколения и разделы соответственно. .NET GC распознает только эти три поколения, потому что проходы коллекции, проходящие через эти три кучи, немного отличаются. Некоторые объекты могут пережить коллекцию, пройденную тысячи раз. GC просто оставляет их на другой стороне раздела кучи Gen 2, нет никакого смысла разбивать их дальше, потому что они на самом деле Gen 44; передача коллекции на них такая же, как и в куче Gen 2.

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


Что в коллекции

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

Теперь, после того как он завершит очистку группы объектов и оставит некоторые в покое, возникнет неприятный побочный эффект: свободные промежутки между живыми объектами, где были удалены мертвые. Эта фрагментация памяти, если оставить ее в покое, просто приведет к потере памяти, поэтому коллекции обычно делают так называемое «сжатие», когда они берут все оставшиеся живые объекты и сжимают их вместе в куче, чтобы свободная память была непрерывной на одной стороне кучи для Gen 0.

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


Коллекция Gen 0

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


Коллекция Gen 1

Бытие 1, являющееся объектами, которые не попадают в эту очень временную категорию объектов, все еще может быть довольно недолгим, потому что большая часть созданных объектов не используется долго. Поэтому Gen 1 также собирает довольно часто, опять же, сохраняя кучу небольшого размера, поэтому сборы происходят быстро. Однако предположение менее из его объектов являются временными , чем Gen 0, поэтому он собирает реже , чем Gen 0

Я скажу, что, честно говоря, я не знаю технических механизмов, которые отличаются между проходом сбора Gen 0 и Gen 1, если они вообще существуют, кроме частоты, которую они собирают.


Gen 2 Collection

Gen 2 теперь должен быть матерью всех куч, не так ли? Ну да, это более или менее правильно. Это место, где живут все ваши постоянные объекты - Main()например, объект, в котором вы живете, и все, на что Main()ссылаются, потому что они будут внедрены до тех пор, пока вы не Main()вернетесь в конце вашего процесса.

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


Куча больших объектов

Одним из примеров дополнительного поведения Gen 2 является то, что он также выполняет сбор данных в куче больших объектов. До сих пор я полностью говорил о куче малых объектов, но среда выполнения .NET выделяет вещи определенного размера в отдельную кучу из-за того, что я назвал выше уплотнением. Сжатие требует перемещения объектов, когда коллекции заканчиваются в куче мелких объектов. Если в Gen 1 есть живой объект размером 10 МБ, ему понадобится гораздо больше времени, чтобы завершить уплотнение после сбора, что замедлит сбор данных в Gen 1. Таким образом, объект размером 10 Мб выделяется для кучи больших объектов и собирается во время второго поколения, которое выполняется так редко.


завершение

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

Механизм, с помощью которого финализаторы делают это, напрямую связан с очередью финализации. Когда среда выполнения выделяет объект с помощью финализатора, он добавляет указатель на этот объект в очередь финализации и блокирует ваш объект на месте (так называемое закрепление), так что сжатие не будет перемещать его, что нарушит ссылку на очередь финализации. По мере прохождения сбора, в конечном итоге будет обнаружено, что ваш объект больше не имеет корня GC, но для его сбора необходимо выполнить финализацию. Поэтому, когда объект мертв, коллекция будет перемещать свою ссылку из очереди завершения и помещать ссылку на него в так называемую очередь «FReachable». Затем коллекция продолжается. В другое «недетерминированное» время в будущем, отдельный поток, известный как поток финализатора, пройдет через очередь FReachable, выполняя финализаторы для каждого из объектов, на которые имеются ссылки. После завершения очередь FReachable становится пустой, и она слегка переворачивает заголовок каждого объекта, который говорит, что им не требуется финализация (этот бит также можно перевернуть вручную с помощьюGC.SuppressFinalizeчто часто встречается в Dispose()методах), я также подозреваю, что это открепило объекты, но не цитируйте меня по этому поводу. Следующая коллекция, находящаяся в куче этого объекта, наконец соберет его. Коллекции Gen 0 даже не обращают внимания на объекты с этим битом, необходимым для финализации, он автоматически продвигает их, даже не проверяя их корень. Некорневый объект, нуждающийся в финализации в Gen 1, будет брошен в FReachableочередь, но коллекция ничего с ним не делает, поэтому он живет в Gen 2. Таким образом, все объекты, которые имеют финализатор, и не GC.SuppressFinalizeбудут собраны в Gen 2.


4
@FlorianMargaine да ... говорить что-либо о "GC" во всех реализациях действительно не имеет смысла ..
Джимми Хоффа

10
tl; dr: вместо этого используйте пулы объектов.
Роберт Харви

5
tl; dr: для синхронизации / профилирования это может быть полезно.
kutschkem

3
@ После прочтения моего описания механики (как я их понимаю), какая польза будет, как вы это видите? Вы убираете большое количество объектов - в SOH (или в LOH?)? Вы только что заставили другие темы сделать паузу для этой коллекции? Разве эта коллекция просто выдвинула вдвое больше объектов в Gen 2, чем она была очищена? Коллекция вызывала уплотнение на LOH (у вас она включена?)? Сколько кучи GC у вас есть, и находится ли ваш GC в режиме сервера или рабочего стола? GC - чертов ледяной айсберг, предательство под водой. Просто держись подальше. Я не достаточно умен, чтобы удобно собирать.
Джимми Хоффа

4
@RobertHarvey Объектные пулы тоже не серебряная пуля. Поколение 0 сборщика мусора уже фактически является пулом объектов - его размер обычно соответствует минимальному уровню кэша, и, таким образом, новые объекты обычно создаются в памяти, которая уже находится в кэше. Ваш пул объектов теперь конкурирует с детским ресурсом GC за кеш, и если сумма детского ресурса GC и вашего пула больше, чем у кеша, вы, очевидно, потеряете кеш. И если вы планируете использовать параллелизм сейчас, вам придется заново реализовать синхронизацию и беспокоиться о ложном совместном использовании.
Доваль

68

К сожалению, никто не уточняет, что такое такие случаи.

Я приведу несколько примеров. В общем, это редко, когда принуждение к ГК - это хорошая идея, но оно того стоит. Этот ответ основан на моем опыте использования .NET и GC литературы. Он должен хорошо распространяться на другие платформы (по крайней мере, те, которые имеют значительный сборщик мусора).

  • Тесты разных видов. Вы хотите известное состояние управляемой кучи, когда начинается эталонный тест, чтобы GC не запускался случайным образом во время эталонных тестов. Когда вы повторяете тест, вы хотите, чтобы в каждом повторении было одинаковое количество и объем работы GC.
  • Внезапное освобождение ресурсов. Например, закрытие значительного окна графического интерфейса или обновление кэша (и, таким образом, освобождение старого потенциально большого содержимого кэша). GC не может обнаружить это, потому что все, что вы делаете, это устанавливаете ссылку на ноль. Тот факт, что это осиротит целый граф объектов, не легко обнаружить.
  • Выпуск неуправляемых ресурсов, которые просочились . Это, конечно, никогда не должно происходить, но я видел случаи, когда сторонняя библиотека просачивалась (например, объекты COM). Разработчик был вынужден иногда вызывать коллекцию.
  • Интерактивные приложения, такие как игры . Во время игр игры имеют очень строгий бюджет времени на кадр (60 Гц => 16 мс на кадр). Чтобы избежать взломов, вам нужна стратегия для борьбы с GC. Одна из таких стратегий заключается в том, чтобы как можно больше задерживать G2 GC и запускать их в подходящее время, например, на экране загрузки или в вырезанной сцене. GC не может знать, когда это лучший момент.
  • Контроль задержки в целом. Некоторые веб-приложения отключают сборщики мусора и периодически запускают сборку G2, не переключаясь из режима балансировки нагрузки. Таким образом, задержка G2 никогда не будет видна пользователю.

Если ваша цель - пропускная способность, чем реже GC, тем лучше. В этих случаях форсирование коллекции не может иметь положительного эффекта (за исключением довольно надуманных проблем, таких как увеличение использования кэша ЦП за счет удаления мертвых объектов, вкрапленных в живые объекты). Сбор партии более эффективен для всех знакомых мне коллекционеров. Для производственного приложения в стационарном режиме потребления памяти индукция ГХ не помогает.

Приведенные выше примеры нацелены на согласованность и ограниченность использования памяти. В этих случаях индуцированные GC могут иметь смысл.

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

Обратите внимание, что затраты на сборку мусора зависят от размера кучи и количества ссылок в куче. На небольшой куче стоимость может быть очень маленькой. Я видел скорость сбора данных G2 с .NET 4.5, равную 1-2 ГБ / с, в производственном приложении с размером кучи 1 ГБ.


В случае контроля задержки, я думаю, вместо того, чтобы делать это периодически, вы также можете сделать это по необходимости (то есть, когда использование памяти превышает определенный порог).
Paŭlo Ebermann

3
+1 для второго до последнего абзаца. Некоторые люди относятся к компиляторам одинаково и быстро называют почти все «преждевременной оптимизацией». Я обычно говорю им что-то подобное.
Хонза Брабек

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

1
@HonzaBrabec Проблема одинакова в обоих случаях: если вы думаете, что знаете лучше, чем сборщик мусора или компилятор, то очень легко навредить себе. Если вы на самом деле знаете больше, тогда вы оптимизируете только тогда, когда знаете, что это преждевременно.
svick

27

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

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


12

Одна вещь, о которой никто не упомянул, - то, что, хотя Windows GC удивительно хороша, GC на Xbox - мусор (каламбур) .

Поэтому, когда вы пишете код для игры XNA, предназначенной для запуска на XBox, абсолютно необходимо выбрать подходящий момент для сбора мусора, иначе у вас будут ужасные периодические сбои FPS. Кроме того, в XBox обычно используют structспособ, гораздо чаще, чем обычно, чтобы минимизировать количество объектов, которые необходимо собирать мусором.


4

Сборка мусора - это прежде всего инструмент управления памятью. Таким образом, сборщики мусора будут собирать, когда есть давление памяти.

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

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

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

RMI

Одним конкретным примером этого является удаленный вызов метода Java. RMI - это библиотека удаленных вызовов процедур. Обычно у вас есть сервер, который делает различные объекты доступными для использования клиентами. Если сервер знает, что объект не используется никакими клиентами, тогда этот объект пригоден для сборки мусора.

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

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

Решение в RMI состоит в том, чтобы клиент периодически запускал сборку мусора, даже когда у него много свободной памяти, чтобы обеспечить своевременный сбор объектов на сервере.


«В этих случаях может иметь смысл запускать сборку мусора вручную, когда вы знаете, что используется ценный ресурс без памяти» - если используется ресурс без памяти, вы должны использовать usingблок или иным образом вызывать Closeметод для убедитесь, что ресурс удален как можно скорее. Использование GC для очистки ресурсов, не связанных с памятью, ненадежно и вызывает всевозможные проблемы (особенно с файлами, которые необходимо заблокировать для доступа, поэтому их можно открыть только один раз).
Жюль

И как указано в ответе, когда closeметод доступен (или ресурс может использоваться с usingблоком), это правильный подход. Ответ конкретно касается редких случаев, когда эти механизмы недоступны.
James_pic

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

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

2

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

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

Однако в некоторых типах обработки вы знаете больше, чем GC. Например, рассмотреть приложение, которое.

  • Дан список имен файлов в командной строке
  • Обрабатывает один файл, а затем записывает результат в файл результатов.
  • Во время обработки файла создается много связанных объектов, которые невозможно собрать, пока не завершится обработка файла (например, дерево разбора).
  • Не сохраняет состояние соответствия между файлами, которые он обработал .

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

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

Единственный раз, когда я рассмотрел бы форсирование коллекции, это когда я знаю, что много объектов было создано недавно, и на очень немногие объекты в настоящее время ссылаются.

Я бы предпочел иметь API для сборки мусора, когда я мог бы дать ему подсказки об этом типе вещей, не заставляя себя собирать GC.

См. Также « Эксперты Рико Мариани »


2

Есть несколько случаев, когда вы можете захотеть вызвать gc () самостоятельно.

  • [ Некоторые люди говорят, что это нехорошо, потому что оно может продвигать объекты в пространство старшего поколения, что, я согласен, не очень хорошо. Однако НЕ всегда верно, что всегда будут объекты, которые можно продвигать. Вполне возможно, что после этого gc()вызова очень мало объектов останется, не говоря уже о том, чтобы перенести их в пространство старшего поколения. ] Когда вы собираетесь создать большую коллекцию объектов и использовать много памяти. Вы просто хотите очистить как можно больше места для подготовки. Это просто здравый смысл. При вызове gc()вручную не будет избыточной проверки ссылочного графа для части этой большой коллекции объектов, которые вы загружаете в память. Короче говоря, если вы запускаете, gc()прежде чем загружать много в память,gc() Индуцированное во время загрузки происходит меньше, по крайней мере, один раз, когда загрузка начинает создавать давление памяти.
  • Когда вы закончили загрузку большой коллекциибольшойобъекты, и вы вряд ли загрузите больше объектов в память. Короче говоря, вы переходите от создания фазы к использованию фазы. При вызове, в gc()зависимости от реализации, используемая память будет сжата, что значительно улучшает локальность кэша. Это приведет к значительному повышению производительности, которое вы не получите от профилирования .
  • Подобно первому, но с точки зрения того, что если вы это сделаете, gc()и реализация управления памятью поддержит, вы создадите намного лучшую непрерывность для вашей физической памяти. Это снова делает новую большую коллекцию объектов более непрерывной и компактной, что, в свою очередь, повышает производительность

1
Может кто-то указать причину понижения? Я сам не знаю достаточно, чтобы судить об ответе (на первый взгляд, для меня это имеет смысл).
Омега

1
Я предполагаю, что вы получили отрицательный голос за третий пункт. Потенциально также за высказывание «Это просто здравый смысл».
immibis

2
Когда вы создаете большую коллекцию объектов, сборщик мусора должен быть достаточно умен, чтобы знать, нужна ли коллекция. То же самое, когда память должна быть сжата. Использование GC для оптимизации локальности памяти связанных объектов не представляется реальным. Я думаю, что вы можете найти другие решения (структура, небезопасно, ...). (Я не downvoter).
Гийом

3
Ваша первая идея хорошего времени - просто плохой совет на мой взгляд. Шансы высоки , что там была коллекция недавно поэтому ваша попытка собирать снова просто собирается произвольно продвигать объекты в последующие поколения, что почти всегда плохо. У более поздних поколений есть коллекции, для начала которых требуется больше времени, увеличивая размеры кучи, «чтобы очистить как можно больше места», просто это становится более проблематичным. Кроме того, если вы собираетесь увеличить нагрузку на память с нагрузкой, вы, скорее всего, начнете вызывать коллекции, которые будут работать медленнее, потому что увеличился Gen1 / 2
Джимми Хоффа

2
By calling gc() depending on implementation, the memory in used will be compacted which massively improves cache locality. This will result in massive improve in performance that you will not get from profiling.Если вы выделяете тонну объектов в строке, то вероятность того, что они уже сжаты. Во всяком случае, сборка мусора может немного перемешать их. В любом случае, использование структур данных, которые плотны и не перепрыгивают случайно в памяти, будет иметь большее влияние. Если вы используете наивный связанный список, состоящий из одного элемента на узел, никакие ручные хитрости GC не восполнят это.
Доваль

2

Пример из реальной жизни:

У меня было веб-приложение, которое использовало очень большой набор данных, которые редко менялись и к которым нужно было обращаться очень быстро (достаточно быстро для ответа на нажатие клавиши через AJAX).

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

Но, будучи очень большой, наивная загрузка заняла бы по меньшей мере 6 ГБ памяти с данными из-за роста в будущем. (У меня нет точных цифр, как только стало ясно, что моя машина на 2 ГБ пытается справиться хотя бы с 6 ГБ, у меня были все необходимые измерения, чтобы знать, что она не будет работать).

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

Все хорошо, но для этого все еще нужно перебрать более 6 ГБ объектов за полминуты, чтобы добраться до этого состояния. Оставленный сам по себе, GC не справился; скачок активности по сравнению с обычным шаблоном приложения (гораздо менее тяжелым по отношению к освобождению в секунду) был слишком резким.

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

Этот пример из реальной жизни является хорошим примером того, когда мы должны использовать GC.Collect():

  1. Используйте с относительно редким случаем, когда множество объектов становятся доступными для сбора (мегабайты были доступны, и такое построение графиков было очень редким случаем в течение всего жизненного цикла приложения (около одной минуты в неделю).
  2. Делайте это, когда потеря производительности относительно терпима; это произошло только при запуске приложения. (Другой хороший пример этого правила - между уровнями во время игры или другими моментами в игре, где игроки не будут расстроены небольшой паузой).
  3. Профиль, чтобы быть уверенным, что действительно есть улучшение. (Довольно просто: «Это работает», почти всегда бьет, «это не работает»).

Большую часть времени, когда я думал, что у меня может быть случай, когда GC.Collect()стоит позвонить, потому что применяются пункты 1 и 2, пункт 3 предполагает, что ситуация ухудшается или, по крайней мере, не становится лучше (и с небольшими или никакими улучшениями я бы склоняться к тому, чтобы не звонить по вызову, поскольку такой подход с большей вероятностью окажется лучше в течение срока службы приложения).


0

У меня есть использование для удаления мусора, что несколько необычно.

Эта ошибочная практика, которая, к сожалению, очень распространена в мире C #, заключается в реализации удаления объектов с использованием уродливой, неуклюжей, не элегантной и склонной к ошибкам идиомы, известной как IDisposable-dispose . MSDN описывает это в деталях , и многие люди клянутся им, следуют этому религиозно, часами часами обсуждают, как именно это должно быть сделано, и т. Д.

(Обратите внимание, что то, что я называю здесь уродливым, - это не сам шаблон удаления объектов; то, что я называю уродливым, - это особая IDisposable.Dispose( bool disposing )идиома.)

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

Но тогда у вас IDisposable.Dispose()могут быть как управляемые, так и неуправляемые объекты для очистки, но управляемые объекты не могут быть очищены при IDisposable.Dispose()вызове из деструктора, потому что они уже были обработаны сборщиком мусора на тот момент, поэтому это необходимость в отдельном Dispose()методе, который принимает bool disposingфлаг, чтобы знать, следует ли очищать как управляемые, так и неуправляемые объекты или только неуправляемые.

Извините, но это просто безумие.

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

Строго говоря, сейчас, конечно, невозможно гарантировать, что ни один программист никогда не совершит ошибку, забыв вызвать IDisposable.Dispose(), но мы можем использовать деструктор, чтобы уловить эту ошибку. На самом деле это очень просто: все, что нужно сделать деструктору, - это сгенерировать запись в журнале, если он обнаружит, что disposedфлаг одноразового объекта никогда не был установлен true. Таким образом, использование деструктора не является неотъемлемой частью нашей стратегии утилизации, но это наш механизм обеспечения качества. И поскольку это тест только в режиме отладки, мы можем поместить весь наш деструктор в #if DEBUGблок, поэтому мы никогда не понесем штрафов за разрушение в производственной среде. ( IDisposable.Dispose( bool disposing )Идиома предписывает, чтоGC.SuppressFinalize() следует вызывать именно для того, чтобы уменьшить накладные расходы на завершение, но с моим механизмом можно полностью избежать этих накладных расходов на производственную среду.)

То, к чему он сводится, - это аргумент вечной жесткой ошибки против мягкой ошибки : IDisposable.Dispose( bool disposing )идиома - это подход мягкой ошибки, и он представляет собой попытку позволить программисту забыть вызывать Dispose()без сбоя системы, если это возможно. Подход с жесткой ошибкой говорит, что программист всегда должен быть уверен, что Dispose()это будет вызвано. Наказание, обычно предписываемое подходом с жесткой ошибкой, в большинстве случаев является ошибкой утверждения, но для этого конкретного случая мы делаем исключение и уменьшаем штраф до простой выдачи записи в журнале ошибок.

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


Now, strictly speaking, it is of course impossible to guarantee that no programmer will ever make the mistake of forgetting to invoke IDisposable.Dispose()На самом деле это не так, хотя я не думаю, что C # способен на это. Не выставляйте ресурс; вместо этого предоставьте DSL для описания всего, что вы будете с ним делать (в основном, монаду), а также функцию, которая получает ресурс, делает вещи, освобождает его и возвращает результат. Хитрость заключается в том, чтобы использовать систему типов, чтобы гарантировать, что, если кто-то переправит ссылку на ресурс, его нельзя будет использовать при другом вызове функции run.
Доваль

2
Проблема с Dispose(bool disposing)(которая не определена в IDisposableтом, что она используется для очистки как управляемых, так и неуправляемых объектов, которые объект имеет как поле (или иным образом отвечает за него), что решает неправильную проблему. Если вы переносите все неуправляемые объекты в управляемом объекте без других одноразовых объектов, о которых можно беспокоиться, тогда все Dispose()методы будут либо одним из них (если финализатор будет выполнять такую ​​же очистку при необходимости), либо будут иметь только управляемые объекты для удаления (не иметь финализатор на всех), и необходимость bool disposingисчезает
Джон Ханна

-1 плохой совет из-за того, как на самом деле работает финализация. Я полностью согласен с вашей точкой зрения на то, что dispose(disposing)идиома «ужасный», но я говорю так, потому что люди так часто используют эту технику и финализаторы, когда у них есть только управляемые ресурсы ( DbConnectionнапример, объект управляется , он не привязан или не прокомментирован), и ВЫ ДОЛЖНЫ ТОЛЬКО КОГДА-ЛИБО РЕАЛИЗОВАТЬ ФИНАЛИЗАТОР С НЕПРАВИЛЬНЫМ, ЗАКРЫТЫМ, КОМ-МАРШАЛЛИЗИРОВАННЫМ ИЛИ НЕ БЕЗОПАСНЫМ КОДОМ . Я подробно описал в своем ответе, как ужасно дороги финализаторы, не используйте их, если у вас нет неуправляемых ресурсов в вашем классе.
Джимми Хоффа

2
Я почти хочу дать вам +1, хотя бы потому, что вы осуждаете что-то, что так много людей считают основной важной вещью в этой dispose(dispoing)идиоме, но правда в том, что это настолько распространено, потому что люди так боятся GC, что что-то не связанное с это ( disposeдолжно быть, имеет отношение к ГК) заслуживает того, чтобы они просто принимали назначенное лекарство, даже не исследуя его. Хорошо, что вы осмотрели его, но вы пропустили самое большое целое (это поощряет финализаторов farrr чаще, чем они должны быть)
Джимми Хоффа

1
@JimmyHoffa спасибо за ваш вклад. Я согласен, что финализатор обычно должен использоваться только для освобождения неуправляемых ресурсов, но разве вы не согласны с тем, что в сборке DEBUG это правило неприменимо и что в сборке DEBUG мы должны быть свободны в использовании финализаторов для выявления ошибок? Это все, что я предлагаю здесь, поэтому я не понимаю, почему вы не согласны с этим. См. Также programmers.stackexchange.com/questions/288715/… для более подробного объяснения этого подхода на Java-стороне мира.
Майк Накис

0

Можете ли вы сказать мне, по какому сценарию на самом деле хорошая или разумная идея форсировать сборку мусора? Я не спрашиваю о конкретных случаях C #, а о всех языках программирования, которые имеют сборщик мусора. Я знаю, что вы не можете заставить GC работать на всех языках, например на Java, но давайте предположим, что вы можете.

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

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

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.