Подтвердить что ты не робот

Какие-либо жесткие данные о GC с явной эффективностью управления памятью?

Недавно я прочитал замечательную статью "" Аналог транзакционной памяти/мусора" Дэн Гроссман. Одно предложение действительно привлекло мое внимание:

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

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

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

Это все экспериментально для меня. Кто-нибудь, и в частности в контексте С++, выполнил всеобъемлющий тест производительности GC при сравнении с явным управлением памятью?

Особенно интересно было бы сравнить, как различные крупные проекты с открытым исходным кодом, например, работают с GC или без него. Кто-нибудь слышал о таких результатах раньше?

EDIT: И сосредоточьтесь на проблеме производительности, а не на том, почему GC существует или почему это выгодно.

Приветствия,

Карл

PS. В случае, если вы уже вытаскиваете пламенем: я не пытаюсь дисквалифицировать GC, я просто пытаюсь получить окончательный ответ на вопрос о производительности.

4b9b3361

Ответ 1

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

http://www.cs.umass.edu/~emery/pubs/04-17.pdf говорит:

"Заключение. Спор о влиянии производительности сборщиков мусора уже давно омрачен разработкой программного обеспечения, которую он предоставляет. В этой статье представлен управляющий и основанный на симуляции диспетчер памяти. Используя эту структуру, мы выполняем ряд неизменных тестов Java, используя как сборку мусора и явное управление памятью. Сравнивая время выполнения, объем пространства и места на виртуальной памяти, мы обнаруживаем, что при большом количестве пространства производительность среды сборки мусора может быть конкурентоспособной с явным управлением памятью и может даже опередить ее на 4% Мы скопируем сборку мусора в шесть раз больше физической памяти, чем распределители Lea или Kingsley, чтобы обеспечить сопоставимую производительность".

Когда у вас достаточно памяти, копирование GC становится быстрее, чем явное free() - http://www.cs.ucsb.edu/~grze/papers/gc/appel87garbage.pdf

Это также зависит от того, какой язык вы используете - Java должен будет много переписывать (стек, объекты, поколения) в каждой коллекции и писать многопоточную GC, которая не должна останавливать мир в JVM, была бы большое достижение. С другой стороны, вы получаете это почти бесплатно в Haskell, где время GC редко составляет > 5%, а время выделения - почти 0. Это действительно зависит от того, что вы делаете и в какой среде.

Ответ 2

Стоимость выделения памяти обычно намного ниже в модели памяти, собранной для мусора, а затем при простом использовании нового или malloc явно, потому что сборщики мусора обычно предварительно выделяют эту память. Однако явные модели памяти также могут делать это (используя пулы памяти или области памяти); что делает стоимость выделения памяти эквивалентной добавлению указателя.

В качестве Raymond Chen и Рико Мариани указал, управляемые языки имеют тенденцию выводить неуправляемые языки в общем случае. Однако, после нажатия на него, неуправляемый язык может и в конечном итоге будет бить язык GC/Jitted.

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

Однако производительность не самая большая разница, когда речь идет о GC против non-GC; возможно, это детерминированная финализация (или RIIA) языков, отличных от GC (и ссылок на подсчет), что является самым большим аргументом для явного управления памятью, поскольку это обычно используется для других целей, кроме управления памятью (таких как освобождение блокировок, закрытие файлов или оконных ручек et cetera). "Недавно", однако С# представила конструкцию using/IDisposable, чтобы сделать именно это.

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

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

Ответ 3

Вот эксперимент, который мне нравится запускать:

  • Запустите программу, написанную в среде сбора мусора (например,.NET или Java).
  • Запустите аналогичную программу, написанную в среде, не содержащей мусор (например, C или С++).
  • Используйте программы и посмотрите, какой из них более отзывчив.

Улучшение объективности: попросите свою бабушку сделать шаг 3.

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

Другим, возможно, противоречивым, примером является IDE Eclipse. Хотя это может быть написано на Java , вся графическая подсистема должна быть переписана, чтобы обеспечить приемлемую производительность. Решение: сделать элементы GUI легкими обертки вокруг собственных (C/С++) компонентов (SWT).

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

Ответ 4

Бергерская бумага цитируется много, но она сравнивает реальные сборщики мусора против чисто теоретического, автономного, оптимального алгоритма. Поэтому, хотя он может рассказать вам кое-что о теоретических пределах, , он говорит очень мало о производительности реальных сборщиков мусора против реальных реализаций malloc и free. Исследование, которое мне больше нравилось, использовало реальные программы и сравнивало явные malloc и free с консервативным сборщиком мусора Хансом Бёмом:

Измеренная стоимость коллекции консервативных мусора от Ben Zorn

Это исследование не является совершенным, и Зорн внимательно следит за тем, что, если программы знали, что они используют сборщик мусора, некоторые из них могут быть сделаны быстрее. Но жесткие данные таковы: - В реальных программах, первоначально написанных для использования malloc и free, собранные с мусором версии работают примерно с одинаковой скоростью, но требуют в два раза больше памяти. Цорн довольно убедительно утверждает, что, если вы знаете, что у вас есть GC, вы можете делать что-то быстрее, но трудно устранить штраф за память.

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

Ответ 5

Здесь есть несколько разных аргументов. Я хочу начать с выяснения, что вы не можете сделать сравнение 1:1. Каждый из них имеет свои плюсы и минусы, и любой фрагмент кода будет более подходящим для одной или другой системы. Это также означает сказать, напротив, что вы должны знать, есть ли у вас GC или нет, чтобы писать эффективный код.

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

Случай:

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

В среде, отличной от GC, для каждого распределения вы должны вызвать malloc, и для этого требуется найти в списке фрагментов свободной памяти наиболее подходящий (в соответствии с конкретной реализацией malloc). Используется память, а затем она освобождается с помощью бесплатного [или нового/удаления в С++...]. Стоимость управления памятью - это затраты на поиск фрагментов.

В среде GC, с подвижным GC как java или .net, после каждого запуска GC вся свободная память смежна. Стоимость приобретения памяти для объекта дешевая, действительно дешевая (< 10 cpu инструкций в Java VM). При каждом запуске GC только живые объекты расположены и перемещаются в начало соответствующей области памяти (обычно это другой регион, чем исходный). Стоимость управления памятью - это прежде всего стоимость перемещения всех доступных (живых) объектов. Теперь предпосылка заключается в том, что большинство объектов недолговечны, поэтому в конце стоимость может быть меньше, чем стоимость системы, отличной от GC. Один миллион объектов, выделенных и освобожденных (забытых) на одном запуске GC без каких-либо дополнительных затрат.

Заключение: на языках GC вы можете создавать все локальные объекты в куче. Они дешевы. С другой стороны, в системах, не относящихся к ГК, куча распределений, освобождение и новые распределения являются дорогостоящими. Память фрагментирована, а стоимость malloc увеличивается... В системах без GC вы должны использовать стек как можно больше, используя кучу по необходимости.

Это имеет другое значение. Люди, привыкшие к одной из двух систем памяти, склонны писать неэффективные программы в другом. Они используются для некоторых идиом, которые, вероятно, плохо относятся к другой системе.

Ясным примером является не управляемый программист, который используется для выделения объекта и повторного использования (reset его внутренние указатели с новыми элементами по мере необходимости) используется для этого способа мышления: распределение дорого, повторное использование дешево. Теперь, если один и тот же точный код переносится в среду GC общего поколения (Java,.net - оба являются mov-generation-GC), вы получаете забавный эффект. В Java generational GC система будет выполнять небольшие коллекции только для младших поколений, обрабатывая только старые поколения в полных коллекциях. Но объект в молодом поколении можно было бы называть объектами в старшем поколении, поэтому необходимо выполнить дополнительную работу, чтобы отслеживать эту информацию от старых до молодых. В частности, в сборщик мусора Java 1.4.1 система отметит карту памяти (подстраницу страницы), где находится старый объект, и она затем включает в себя все отмеченные карточки для обработки во время небольшой коллекции, эффективно увеличивая объем работы, которую GC должен выполнять и, возможно, влиять на производительность.

Объект был жив в течение 1, 2, 3... GC работает, и он был перемещен так много раз, наконец, перемещен в прежнее поколение, где он не будет перемещаться в каждом сеансе GC, а может просто стоять там... но, увы, программист заставляет объект становиться моложе. Он снова перемещается, и он снова будет перемещаться каждый раз, когда GC запускается до времени, когда он становится старым снова.

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

Ответ 6

GC всегда будет медленнее, чем экстремальная альтернатива: идеальное, не детерминированное управление памятью.

Вопросы:

  • Являются ли различия достаточно большими, чтобы придираться?
  • Являются ли недостатки одного метода достаточными, чтобы заставить нас серьезно относиться к другому?

Существуют и другие области, в которых управляемые подсистемы побеждали над неуправляемыми:

В общем, программа будет работать медленнее в многозадачной операционной системе, чем на однозадачном, или на компьютере без ОС.

В общем, программа всегда будет работать медленнее в системе с виртуальной памятью, чем на одном без.

За исключением чрезвычайных обстоятельств, серьезно ли мы рассматриваем компьютерные системы без ВМ и без ОС?

Ответ 7

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

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

Во-первых, это часто происходит, когда размер рабочего набора близок к максимальному размеру кучи. GC вызывается слишком часто, и, следовательно, все замедляется. Установите плагин Scala для Eclipse, и вы это почувствуете.

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

Вот пример исключения: возьмем "map f list" и предположим, что каждое приложение f потребляет живую память, сохраняя ссылку на возвращаемое значение в хеш-таблице. Здесь асимптотическая сложность должна быть O (1).

Генерация GC не сможет освободить память, собирая питомник, поэтому большая коллекция (O (куча содержимого)) вызывается несколько периодически. Следовательно, мы получаем, что время выполнения по крайней мере пропорционально (размер содержимого кучи) * n * (пробел, потребляемый каждым вызовом f)/(размер ясли).

GC фактически увеличит размер яслей до указанного максимума, а затем выше будет снова для n достаточно большим. Однако, если указанным максимумом является Big-Theta (максимальный размер кучи) и, таким образом, Omega (размер содержимого кучи), основные коллекции становятся нечастыми, а стоимость небольших коллекций пропорциональна полученным данным (и, следовательно, времени выполнения, необходимого для производства их). Это похоже на то, когда вам нужно скопировать контейнер, чтобы изменить его размер: достаточно увеличив его, вы можете амортизировать затраты на копирование (или GC) и сделать его сопоставимым с затратами, необходимыми для создания копии.

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

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

Ответ 8

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

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

Ответ 9

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

С системой GC, когда ваш хороший код O (N) оказывается мусором GC патологически, что делает его O (размер кучи), может быть сложнее определить, что происходит не так. Иногда даже трудно зафиксировать повреждение памяти.

Ответ 10

Фундаментальное различие между gc и любой реализацией malloc заключается в том, что gc отслеживает выделенные объекты, в то время как malloc в основном отслеживает освобожденные объекты (через "свободные списки" и т.д., которые необходимы для сбора освобожденных блоков памяти, чтобы быстро вернуть их на следующих malloc) - некоторые реализации malloc даже не имеют возможности (в своей внутренней части) перечислять все выделенные блоки по дизайну. Как следствие, любая возможная реализация gc (независимо от того, насколько она может быть), всегда будет иметь сложность O (somefunc (N)), где N - количество выделенных объектов, тогда как большинство реализаций malloc имеют сложность O (1). Таким образом, когда количество (одновременно стоящих) выделенных объектов увеличивается все больше, деградация производительности любого gc неизбежна (но, конечно, производительность может быть продана для дополнительного потребления памяти). [В конце концов, очевидно, что блоки свободной памяти всегда имеют нулевые служебные расходы, в отличие от выделенных блоков/объектов.] Это фундаментальный недостаток любого gc, поскольку он собирает поддерживает мусор:), в то время как malloc поддерживает nongarbage (:.

P.S. По malloc я имею в виду не только определенную так называемую функцию C, но, скорее, любую явную процедуру распределения памяти. Кроме того, я хотел бы отметить, что языки программирования без встроенного gc предлагают множество способов обхода явных вызовов malloc/free (new/delete) (например, std:: shared_ptr в С++ или ARC в Obj-C) что делает программный код похожим на gc-powered языки, но с точки зрения производительности он намного ближе (почти эквивалентен) к явному распределению памяти. (Я знаю, что даже простой подсчет ссылок можно рассматривать как форму сбора мусора, но в этом сообщении gc я подразумеваю любую более функциональную реализацию (по крайней мере, с автоматическим определением эталонных циклов), которые требуют отслеживания всех выделенные объекты, поэтому я не рассматриваю такие оболочки как std:: shared_ptr как gc (по крайней мере, в этом сообщении)).

Ответ 11

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

Ответ 12

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

Ответ 13

Как указывает @dribeas, самая большая "смешана" с экспериментом в документе (Hertz & Berger) заключается в том, что код всегда записывается под некоторыми "неявными предположениями" о том, что дешево и что дорого. Помимо этого, экспериментальная методология (запуск Java-программы в автономном режиме, создание оракула жизни объектов, возврат инструмента в "идеальные" вызовы free/free) на самом деле довольно блестящий и освещающий. (И мое личное мнение состоит в том, что смешение не отвлекает слишком много от их результатов.)

Лично я чувствую, что использование среды GC-ed означает, что вы принимаете три фактора производительности вашего приложения (GC'd будет в 3 раза медленнее). Но реальный ландшафт программ усеян путаницами, и вы, вероятно, найдете огромный рассеянный объем данных, если бы вы могли выполнить "идеальный" эксперимент по множеству программ во многих областях приложений, причем GC иногда выигрывал, а Manual часто выигрывал, (И пейзаж постоянно меняется - изменятся ли результаты, когда многоядерность (и программное обеспечение, предназначенное для многоядерных) является основным?)

См. также мой ответ на

Существуют ли статистические исследования, которые показывают, что Python является более продуктивным?

у которого есть тезис о том, что "из-за стольких путаниц все доказательства разработки программного обеспечения являются анекдотическими".

Ответ 14

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

За исключением длительных серверных приложений, на практике у вас никогда не будет нехватка памяти, ОС начнет использовать диск для виртуальной памяти (машины имеют практически бесконечную память, вплоть до пределов виртуального адресного пространства, которое я теперь думаю, что это огромные 64-битные машины). Это подчеркивает, что GC - не что иное, как устройство для улучшения локальности. Утечка/мертвые объекты не "повреждаются", когда у вас бесконечная память, за исключением того, что память идет в иерархии, и вы хотите сохранить "живые" объекты рядом и быстро и "мертвые" объекты в далекой/медленной памяти. Если каждый объект был выделен на другой странице, то система виртуальной памяти ОС была бы эффективной GC.

Ответ 15

См. также

http://prog21.dadgum.com/40.html

в котором обсуждается "достаточно умный" компилятор. Пейзаж CS/программного обеспечения пронизан идеями/техникой, которые могут быть в теории более эффективными, чем статус-кво. Но все это змеиное масло.

GC сегодня стоит дорого и всегда может быть.