В GC или не в GC - PullRequest
       47

В GC или не в GC

70 голосов
/ 28 сентября 2011

Я недавно видел два очень хороших и обучающих разговора по языкам:

Этот первый от Херба Саттера, представляет все приятные и классные функции C ++ 0x, почему C ++будущее кажется ярче, чем когда-либо, и как говорят, M $ - хороший парень в этой игре.Речь идет об эффективности и о том, как минимизация активности кучи очень часто повышает производительность.

Этот другой , автор Андрея Александреску, мотивирует переход из C / C ++ в егоновый игровой автомат D .Большая часть материала D кажется действительно мотивированной и продуманной.Одна вещь, однако, удивила меня, а именно, что D подталкивает к сборке мусора и что все классы создаются исключительно по ссылке .Еще более запутанно то, что в книге Справочник по языку программирования D , в частности в разделе о Управление ресурсами , говорится следующее, цитата:

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

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

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

  2. В каких случаях мы можем использовать сборщик мусора без ущерба для производительности во время выполнения?

Ответы [ 10 ]

45 голосов
/ 28 сентября 2011

Чтобы ответить прямо на два вопроса:

  1. Да, создание экземпляров класса по ссылке приводит к большой активности кучи, , но :

    а. В D у вас есть struct, а также class. struct имеет семантику значений и может делать все, что может делать класс, кроме полиморфизма.

    б. Полиморфизм и семантика значений никогда не работали вместе из-за проблемы среза .

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

  2. GC может быть сопоставим или быстрее, чем ручное управление памятью, если:

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

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

    с. Вы выделяете в основном небольшие объекты. Если вы выделяете в основном большие массивы и производительность оказывается проблемой, вы можете переключить некоторые из них на кучу C (у вас есть доступ к malloc C и свободен в D) или, если он имеет ограниченный срок службы, некоторые другие распределитель типа RegionAllocator . (RegionAllocator в настоящее время обсуждается и дорабатывается для возможного включения в стандартную библиотеку D).

    * * +1034 д. Вы не заботитесь о космической эффективности. Если вы заставите GC работать слишком часто, чтобы сохранить объем памяти слишком низким, производительность пострадает.
22 голосов
/ 28 сентября 2011

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

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

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

13 голосов
/ 28 сентября 2011

Ответ на 1):

Пока ваша куча смежных , распределение на ней так же дешево, как и на стеке.

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

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

Это хорошая новость:)

Ответ на 2):

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

Так что если

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

Возможно, вы получите лучшую производительность.

Ответ на незаданный вопрос:

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

4 голосов
/ 29 сентября 2011

Другим важным моментом является правило 80:20.Вполне вероятно, что это подавляющее большинство мест, которые вы выделяете, не имеет значения, и вы не получите большого выигрыша от GC, даже если бы вы могли довести стоимость до нуля.Если вы принимаете это, то простота, которую вы можете получить, используя GC, может сместить стоимость его использования.Это особенно верно, если вы можете избежать копирования.D обеспечивает GC для 80% случаев и доступ к выделению стека и malloc для 20%.

4 голосов
/ 28 сентября 2011

Это не «сборщик мусора» или «утомительный, подверженный ошибкам» рукописный код. Умные указатели, которые действительно умны, могут дать вам семантику стека и означают, что вы никогда не вводите «удалить», но вы не платите за сборку мусора. Вот еще одно видео от Herb , в котором главное - безопасно и быстро - это то, что мы хотим.

3 голосов
/ 28 сентября 2011

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

Приличный компилятор почти наверняка выделит x в стеке в следующем примере:

void f() {
    Foo* x = new Foo();
    x->doStuff(); // Assuming doStuff doesn't assign 'this' anywhere
    // delete x or assume the GC gets it
}

То, что делает компилятор, называется escape анализ .

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

3 голосов
/ 28 сентября 2011

Даже если бы у вас был идеальный сборщик мусора, он все равно был бы медленнее, чем создание вещей в стеке.Таким образом, вы должны иметь язык, который позволяет оба языка одновременно.Кроме того, единственный способ добиться той же производительности с помощью сборщика мусора, что и при распределении памяти, управляемом вручную (сделано правильно), состоит в том, чтобы заставить его выполнять те же действия с памятью, как это делал бы опытный разработчик, и что во многих случаяхтребует, чтобы решения сборщика мусора принимались во время компиляции и выполнялись во время выполнения.Обычно сборка мусора замедляет работу, языки, работающие только с динамической памятью, работают медленнее, а предсказуемость выполнения программ, написанных на этих языках, низкая, а задержка выполнения выше.Честно говоря, я лично не понимаю, зачем нужен сборщик мусора.Управлять памятью вручную не сложно.По крайней мере, не в C ++.Конечно, я не буду возражать против того, чтобы компилятор генерировал код, который очищает все для меня, как я бы сделал, но в данный момент это кажется невозможным.

2 голосов
/ 17 апреля 2013

Инкрементальный сборщик мусора с низким приоритетом будет собирать мусор, когда задача с высоким приоритетом не выполняется.Потоки с высоким приоритетом будут работать быстрее, поскольку освобождение памяти не будет выполнено.Это идея RT Java GC Хенрикссона см. http://www.oracle.com/technetwork/articles/javase/index-138577.html

1 голос
/ 28 сентября 2011

Сборка мусора на самом деле замедляет код. Это добавляет дополнительные функции в программу, которая должна работать в дополнение к вашему коду. Есть и другие проблемы с ним, такие как, например, GC не работает до тех пор, пока память фактически не понадобится. Это может привести к небольшим утечкам памяти. Другая проблема заключается в том, что если ссылка не будет удалена должным образом, ГХ не будет ее поднимать, что снова приведет к утечке. Моя другая проблема с GC заключается в том, что она как бы продвигает лень программистам. Я сторонник изучения низкоуровневых концепций управления памятью, прежде чем перейти на более высокий уровень. Это как математика. Вы узнаете, как найти корни квадратичного или как сначала взять производную вручную, а затем научитесь делать это на калькуляторе. Используйте эти вещи как инструменты, а не костыли.

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

0 голосов
/ 07 июня 2013

Моя точка зрения заключается в том, что GC уступает malloc, когда вы выполняете нормальное процедурное программирование.Вы просто переходите от процедуры к процедуре, выделяете и освобождаете, используете глобальные переменные и объявляете некоторые функции _inline или _register.Это стиль C.

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

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

Вы упомянули D, но D все еще улучшен в C ++, поэтому вероятно, что malloc или подсчет ссылок в конструкторах, выделение стека, глобальные переменные (даже если они являются сложными деревьями сущностей всех видов)Вы выбираете.

...