Количественная оценка эффективности сбора мусора против явного управления памятью

Я нашел эту статью здесь:

Количественная оценка эффективности сборки мусора против явного управления памятью

http://www.cs.umass.edu/~emery/pubs/gcvsmalloc.pdf

В разделе заключения он гласит:

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

Итак, если мое понимание верное: если у меня есть приложение, написанное на родном С++, требующее 100 МБ памяти, для достижения такой же производительности с помощью "управляемого" (т.е. сборщика мусора) языка (например, Java, С#), приложение должно требовать 5 * 100 МБ = 500 МБ? (И с 2 * 100 МБ = 200 МБ управляемое приложение будет работать на 70% медленнее, чем собственное приложение?)

Знаете ли вы, что текущие (то есть самые последние Java VM и .NET 4.0) сборщики мусора испытывают те же проблемы, описанные в вышеупомянутой статье? Улучшена ли производительность современных сборщиков мусора?

Спасибо.

Ответы

Ответ 1

Кажется, вы задаете две вещи:

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

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

  • Для управления памятью в GC требуется значительное (например, от 3 до 5 раз) больше виртуальной памяти.
  • Если вы пытаетесь ограничить размер кучи, производительность GC значительно падает.
  • Если реальная память ограничена, подход к управлению памятью GC'ed приводит к значительно худшей производительности из-за служебных сообщений пейджинга.

Однако выводы не могут быть использованы в качестве формулы:

  • Исходное исследование было выполнено с JikesRVM, а не с Sun JVM.
  • Сборщики мусора Sun JVM улучшились за 5 лет после исследования.
  • Исследование, похоже, не учитывает, что структуры данных Java занимают больше места, чем эквивалентные структуры данных С++ по причинам, не связанным с GC.

В последнем пункте я видел презентацию того, кто говорит о накладных расходах Java. Например, он обнаружил, что минимальный размер представления строки Java составляет примерно 48 байт. (Строка состоит из двух примитивных объектов: одного - с четырьмя полями по размеру слова, а другой - с минимальным количеством слов на 1 слово. Каждый примитивный объект также имеет 3 или 4 слова служебных данных.) Структуры данных коллекции Java аналогично использовать гораздо больше памяти, чем люди понимают.

Эти накладные расходы не связаны с GC. Скорее, это прямые и косвенные последствия дизайнерских решений в Java, JVM и библиотеках классов. Например:

  • Каждый заголовок Java примитивного объекта резервирует одно слово для значения "идентификатор hashcode" объекта и другое слово для представления блокировки объекта.
  • Представление String должно использовать отдельный "массив символов" из-за ограничений JVM. Два из трех других полей - попытка сделать операцию substring менее интенсивной в памяти.
  • Типы коллекции Java используют большую память, потому что элементы коллекции не могут быть напрямую привязаны. Так, например, накладные расходы (гипотетического) отдельного класса коллекции списков в Java будут составлять 6 слов в элементе списка. Напротив, оптимальный связанный с C/С++ список (т.е. С каждым элементом, имеющим "следующий" указатель) имеет накладные расходы на одно слово для элемента списка.

Ответ 2

если у меня есть приложение, написанное на родном С++ требующих 100 МБ памяти, для достижения такая же производительность с "управляемым" (т.е. сборщик мусора) язык (например, Java, С#), приложение должен требовать 5 * 100 МБ = 500 МБ? (А также с 2 * 100 МБ = 200 МБ, управляемый приложение будет работать на 70% медленнее, чем родное приложение?)

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

Ответ 3

Майкл Боргвардт прав, если приложение является узким местом при распределении памяти. Это согласно закону Амдала.

Однако я использовал С++, Java и VB.NET. В С++ существуют мощные методы, которые выделяют память в стеке вместо кучи. Выделение стека легко в сотни раз быстрее, чем распределение кучи. Я бы сказал, что использование этих методов может удалить, возможно, одно распределение в восемь, а использование записываемых строк - одно выделение в четыре раза.

Это не шутка, когда люди утверждают, что оптимизированный код на С++ может привести к наилучшему возможному Java-коду. Это плоская истина.

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

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

Ответ 4

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

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

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

Ответ 5

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

BTW: В С++ есть много тиков, которые поддерживают это, которые недоступны на Java.

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

Стоимость основной памяти не такая уж большая проблема, как и для меня. В наши дни машина с 48 ГБ является относительно дешевой. 8-ядерный сервер с 48 ГБ основной памяти можно арендовать за 9 фунтов стерлингов в день. Попробуйте нанять разработчика за 9 фунтов стерлингов.;) Однако то, что по-прежнему относительно дорого, это кэш-память процессора. Достаточно сложно найти систему с более чем 16 Мб кэша процессора. c.f. 48 000 МБ основной памяти. Система работает намного лучше, когда приложение использует свой кэш процессора, и это объем памяти, который следует учитывать, если производительность критическая.