Альтернатива для сборщика мусора - PullRequest
13 голосов
/ 08 апреля 2010

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

Я работаю над безопасным языком (т. Е. Языком без висячих указателей, проверки границ и т. Д.), А также сборкой мусора илидолжна использоваться его альтернатива.

Ответы [ 8 ]

15 голосов
/ 12 мая 2010

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

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

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

Примером очень быстрого специализированного подхода к управлению памятью является распределитель «на кадр» , используемый во многих играх. Это работает путем увеличения одного указателя для выделения памяти, и в конце периода времени (обычно визуального «кадра») все объекты отбрасываются сразу, просто возвращая указатель на базовый адрес и перезаписывая их при следующем выделении. , Это может быть «безопасно», однако ограничения времени жизни объекта будут очень строгими. Может быть победителем, если вы можете гарантировать, что все распределение памяти ограничено по размеру и действительно только для объема обработки, например один запрос к серверу.

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

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

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

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

В любом случае - надеюсь, это полезная пища для размышлений!

4 голосов
/ 08 апреля 2010

Если скорость имеет значение, а память - нет, то самая быстрая и простая стратегия распределения - никогда не освобождать. Распределение - это просто вопрос увеличения указателя. Вы не можете получить быстрее, чем это.

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

Для более общих решений, если вы хотите распределение в реальном времени (то есть гарантированные ограничения времени ответа на запросы выделения), тогда сбор мусора - это путь. GC в реальном времени может выглядеть следующим образом: объекты выделяются с помощью стратегии увеличения указателя. Кроме того, при каждом выделении распределитель выполняет небольшую сборку мусора, в которой «живые» объекты копируются в другое место. Таким образом, GC работает «одновременно», чем приложение. Это подразумевает дополнительную работу для доступа к объектам, потому что вы не можете переместить объект и обновить все указатели, чтобы они указывали на новое местоположение объекта, сохраняя обещание «в реальном времени». Решения могут подразумевать барьеры , например дополнительная косвенность. GC поколений обеспечивает беспрепятственный доступ к большинству объектов, сохраняя при этом время паузы в строгих рамках.

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

1 голос
/ 30 июня 2010

В качестве «альтернативы» для сборки мусора, C ++ имеет специальные умные указатели.boost :: shared_ptr <> (или std :: tr1 :: shared_ptr <>) работает точно так же, как сборка мусора Python с подсчетом ссылок.На мой взгляд, shared_ptr - это сборка мусора.(хотя вам, возможно, потребуется сделать несколько штук weak_ptr <>, чтобы избежать циклических ссылок)

Я бы сказал, что auto_ptr <> (или в C ++ 0x - unique_ptr <>...) является жизнеспособной альтернативой, со своим собственным набором преимуществ и компромиссов.Auto_ptr имеет неуклюжий синтаксис и не может использоваться в контейнерах STL ... но он выполняет свою работу.Во время компиляции вы «перемещаете» владение указателем из переменной в переменную.Если переменная владеет указателем, когда выходит из области видимости, она вызывает свой деструктор и освобождает память.Только одному auto_ptr <> (или unique_ptr <>) разрешено иметь реальный указатель.(по крайней мере, если вы используете его правильно).

В качестве другой альтернативы вы можете хранить все в стеке и просто передавать ссылки на все нужные вам функции.

Эти альтернативы не решают общую проблему управления памятью, которую решает сборка мусора.Тем не менее, они эффективны и хорошо проверены.Auto_ptr не использует больше места, чем указатель изначально ... и нет никаких накладных расходов на разыменование auto_ptr.«Движение» (или присваивание в Auto_ptr) имеет незначительные накладные расходы для отслеживания владельца.Я не делал никаких тестов, но уверен, что они быстрее, чем сборщик мусора / shared_ptr.

1 голос
/ 08 апреля 2010

С C ++ можно сделать выделение кучи ОДИН РАЗ для ваших объектов, а затем повторно использовать эту память для последующих объектов, я видел, как это работает, и это было невероятно быстро.

Это применимо только к определенному наборупроблем, и это трудно сделать правильно, но это возможно.

Одна из радостей C ++ в том, что у вас есть полный контроль над управлением памятью, вы можете решить использовать классическое new / delete или реализовать свойсобственный подсчет ссылок или сборщик мусора.

Однако - будьте драконами - вам действительно, действительно нужно знать, что вы делаете.

0 голосов
/ 14 ноября 2017

Если вы действительно не хотите никаких пауз вообще, запретите все выделение памяти, за исключением выделения стека, региональных буферов и статического выделения. Несмотря на то, что вам, возможно, сказали, malloc() может вызвать серьезные паузы, если список свободных фрагментов фрагментируется, и если вы часто обнаруживаете, что строите массивные графы объектов, наивное ручное освобождение может и будет проигрывать при остановке и копировании; единственный способ действительно избежать этого - амортизация предварительно выделенных страниц, таких как стек или выделенный пул, который освобождается одновременно. Я не знаю, насколько это полезно, но я знаю, что проприетарный язык графического программирования LabVIEW по умолчанию выделяет статическую область памяти для каждого эквивалента подпрограммы, что требует от программистов ручного включения выделения стека; Это такая вещь, которая полезна в жестких условиях реального времени, где вам нужны абсолютные гарантии на использование памяти.

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

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

0 голосов
/ 12 мая 2010

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

Эта проблема возникла во время разработкиигры в Java / C #, и решение состояло в том, чтобы использовать пул памяти и не дать объекту умереть, следовательно, не требуется сборщик мусора для запуска, когда вы этого не ожидаете.Это действительно тот же подход, что и в случае неуправляемых систем с малой задержкой - ПОПРОБУЙТЕ ДЕЙСТВИТЕЛЬНО НАСТОЯЩЕМУ, НЕ ВЫДЕЛАТЬ ПАМЯТЬ.

Итак, принимая во внимание тот факт, что реализация такой системы в Java / C # очень похожа на C ++, преимущество в том, что она делает это по-девчачьи (управляемой), у вас есть «приятность» других языковых возможностей, которыеваши умственные циклы часов, чтобы сосредоточиться на важных вещах.

0 голосов
/ 12 мая 2010

Если память не имеет значения, то применимо то, что говорит @Thomas. Учитывая огромные пространства памяти современного оборудования, это вполне может быть жизнеспособным вариантом - это действительно зависит от процесса.

Ручное управление памятью не обязательно решает ваши проблемы напрямую, но дает вам полный контроль над тем, КОГДА происходят события в памяти. Например, общий malloc не является операцией O (1). Он делает там все возможные потенциально ужасные вещи, как в куче, управляемой самим malloc, так и операционной системой. Например, вы никогда не знаете, когда «malloc (10)» может привести к тому, что виртуальная машина выгружает что-то, теперь ваши 10 байтов оперативной памяти имеют неизвестный компонент ввода-вывода диска - упс! Хуже того, эта страница может быть ВАШЕЙ памятью, которую вам нужно немедленно вернуть обратно! Теперь c = * p - удар по диску. YAY!

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

С системой GC у вас может быть похожая опция - это зависит от коллектора. Я не думаю, что Sun JVM, например, обладает способностью «выключаться» на короткие промежутки времени. Но если вы работаете с предварительно распределенными структурами и вызываете весь свой собственный код (или точно знаете, что происходит в вызываемой вами библиотечной подпрограмме), у вас, вероятно, есть хороший шанс не попасть в диспетчер памяти.

Потому что суть дела в том, что управление памятью - это большая работа. Если вы хотите избавиться от управления памятью, напишите старый школьный FORTRAN с массивами ARRAY и COMMON (одна из причин, по которой FORTRAN может быть очень быстрым). Конечно, вы можете написать «Фортран» практически на любом языке.

С современными языками, современными GC и т. Д. Управление памятью было отодвинуто и стало проблемой «10%». Сейчас мы довольно небрежны с созданием мусора, копированием памяти и т. Д. И т. Д., Потому что GC и др. Позволяют нам быть небрежными. И для 90% программ это не проблема, поэтому мы не беспокоимся. В настоящее время это проблема с настройкой, запоздалая.

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

0 голосов
/ 08 апреля 2010

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

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