Как реализовать закрытие без gc?

Я разрабатываю язык. Во-первых, я хочу решить, какой код сгенерировать. Язык будет иметь лексические замыкания и основанное на прототипе наследование, подобное javascript. Но я не поклонник gc и стараюсь избегать как можно большего. Итак, вопрос: есть ли элегантный способ реализовать закрытие, не прибегая к распределению фрейма стека в куче и оставить его сборщику мусора?

Мои первые мысли:

  • Использовать подсчет ссылок и мусор собирать циклы (мне это не очень нравится)
  • Используйте пакет спагетти (выглядит очень неэффективно)
  • Предельное формирование замыканий в некоторых контекстах таким образом, что я могу уйти с стеком обратного адреса и стекю locals.

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

(Edit: Я знаю, что подсчет ссылок - это форма сбора мусора, но я использую gc в более общем смысле)

Ответ 1

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

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

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

Короткий вариант: у вас есть три основных варианта:

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

Ответ 2

Этот поток может помочь, хотя некоторые из ответов здесь отражают ответы там уже.

Один плакат дает хорошую оценку:

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

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

Итак, мой вопрос отражает некоторые из других здесь - почему вы не хотите внедрять GC? Простая метка + развертка или стоп + копия занимает около 2-300 строк кода (Scheme), и на самом деле это не так плохо с точки зрения усилий по программированию. С точки зрения замедления ваших программ:

  • Вы можете реализовать более сложный GC, который имеет лучшую производительность.
  • Просто подумайте, что все программы утечки памяти на вашем языке не пострадают.
  • Кодирование с доступным GC является благословением. (Think С#, Java, Python, Perl и т.д.) Или С++ или C).

Ответ 3

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

Я считаю, что полная поддержка закрытия действительно требует GC, но в некоторых особых случаях распределение стека безопасно. Для определения этих особых случаев требуется анализ эвакуации. Я предлагаю вам взглянуть на документы на языке БиТК, такие как Closure Реализация в BitC. (Хотя я сомневаюсь, что документы отражают текущие планы.) У дизайнеров BitC была та же проблема, что и вы. Они решили реализовать специальный не-сборщик для компилятора, который отрицает все блокировки, которые могут уйти. Если он включен, это значительно ограничит язык. Однако эта функция еще не реализована.

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

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

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

Ответ 4

Спецификация С++ 0x определяет lambdas без сбора мусора. Короче говоря, спецификация допускает недетерминированное поведение в тех случаях, когда лямбда-замыкание содержит ссылки, которые более недействительны. Например (псевдосинтаксис):

(int)=>int create_lambda(int a)
{
    return { (int x) => x + a }
}

create_lambda(5)(4)    // undefined result

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

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

Ответ 5

Использовать подсчет ссылок и мусор собирать циклы (мне это действительно не нравится)

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

Ответ 6

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

Вы также можете взглянуть на подход С++ 0x (N1968), хотя, как можно было бы ожидать от С++, он состоит из подсчета на программиста, чтобы указать, что копируется, и на что ссылаются, и если вы ошибаетесь, вы просто получаете недействительные обращения.

Ответ 7

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

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

@Allen

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

Ответ 8

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

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

Ответ 9

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

GC - единственное решение для общего случая.

Ответ 10

Лучше поздно, чем никогда?

Вам может показаться интересным: Дифференциальное исполнение.

Это малоизвестная контрольная структура, и ее основное использование - в программировании пользовательских интерфейсов, в том числе тех, которые могут динамически меняться во время использования. Это значительная альтернатива парадигме Model-View-Controller.

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

Ответ 11

Создать несколько стеков?

Ответ 12

Я читал, что последние версии ML используют GC только экономно

Ответ 13

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