Как реализовать закрытие без 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, конечно же, не является большим препятствием.