2010-02-25 3 views
6

Функция:SbCl работает вечно на втором вызове функции

дан список возврата LST все перестановки содержимого входящих в список точно длины к, который по умолчанию длина списка, если не предусмотрено.

(defun permute (lst &optional (k (length lst))) 
    (if (= k 1) 
    (mapcar #'list lst) 
    (loop for item in lst nconcing 
    (mapcar (lambda (x) (cons item x)) 
      (permute (remove-if (lambda (x) (eq x item)) lst) 
         (1- k)))))) 

Проблема: Я использую SLIME в Emacs, связанных с SBCL, я не сделал слишком много настроек еще. Функция отлично работает на меньших входах, таких как lst = '(1 2 3 4 5 6 7 8) k = 3, что и будет использоваться на практике. Однако, когда я вызываю его с большим вводом дважды подряд, второй вызов никогда не возвращается и sbcl даже не отображается вверху. Это результаты в REPL:

CL-USER> (time (nth (1- 1000000) (permute '(0 1 2 3 4 5 6 7 8 9)))) 
Evaluation took: 
12.263 seconds of real time 
12.166150 seconds of total run time (10.705372 user, 1.460778 system) 
[ Run times consist of 9.331 seconds GC time, and 2.836 seconds non-GC time. ] 
99.21% CPU 
27,105,349,193 processor cycles 
930,080,016 bytes consed 

(2 7 8 3 9 1 5 4 6 0) 
CL-USER> (time (nth (1- 1000000) (permute '(0 1 2 3 4 5 6 7 8 9)))) 

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

+0

Что-нибудь интересное в вашем * нижнем lisp * буфере, когда это происходит? – Xach

+0

Почему бы не прервать SBCL и посмотреть на обратную линию, что он делает? –

+0

Как общий вопрос всем, кто ответил.Это похоже на то количество мусора, которое я создаю, это проблема. Есть ли хорошие статьи, объясняющие, как обойти такие вещи? Я сделал некоторые вещи, которые, как я думал, помогли бы, но повсеместно они на самом деле сделали это намного хуже. – asm

ответ

4

В коде есть что-то неправильное - это использование EQ. EQ сравнивается для идентичности.

EQ не для сравнения цифр. EQ двух чисел может быть истинным или ложным.

Используйте EQL, если вы хотите сравнить его по идентификаторам, цифрам по значению или символам. Не EQ.

На самом деле

(remove-if (lambda (x) (eql x item)) list) 

просто

(remove item list) 

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

Кроме этого, я думаю, что SBCL просто занят управлением памятью. SBCL на моем Mac приобрел много памяти (более GB) и был занят чем-то занятым. Через некоторое время результат был вычислен.

Ваша рекурсивная функция генерирует огромное количество «мусора». LispWorks говорит: 1360950192 bytes

Возможно, вы можете придумать более эффективную реализацию?

Обновление: мусора

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

Lisp использует как стек, так и кучу для выделения памяти. Куча может быть структурирована определенными способами для GC - например, в поколениях, полупространствах и/или областях. Существуют точные сборщики мусора и «консервативные» сборщики мусора (используемые SBCL на машинах Intel).

Когда программа работает, мы можем увидеть различные эффекты:

  1. нормальные рекурсивные процедуры выделения места на стеке. Проблема: размер стека обычно фиксируется (хотя некоторые Lisps могут увеличить его в обработчике ошибок).

  2. Программа может выделять огромный объем памяти и возвращать большой результат. PERMUTE - такая функция. Он может возвращать очень большие списки.

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

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

  1. Заменить рекурсию итерацией. Замените рекурсию хвостовой рекурсией.

  2. Возвратите только ту часть результата, которая необходима и не генерирует полное решение. Если вам нужна n-я перестановка, тогда вычислите, что и не все перестановки. Используйте ленивые структуры данных, которые вычисляются по требованию. Используйте что-то вроде SERIES, которое позволяет использовать потоковые, но эффективные вычисления. См. SICP, PAIP и другие продвинутые книги Lisp.

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

Выше сделок с космическими проблемами реальных программ. В идеале наши компиляторы или среда выполнения могут обеспечить некоторую автоматическую поддержку для решения этих проблем. Но на самом деле это не работает. Большинство систем Lisp обеспечивают низкоуровневую функциональность для решения этой проблемы, а Lisp предоставляет изменяемые объекты - поскольку опыт реальных программ Lisp показал, что программисты хотят использовать их для оптимизации своих программ. Если у вас есть большое приложение САПР, которое вычисляет форму лопаток турбины, то теоретические/пуристические представления о не изменяемой памяти просто не применяются - разработчику требуется более быстрый/меньший код и меньший размер времени выполнения.

+0

Я правильно понимаю, что рекурсивная реализация генерирует огромное количество мусора, потому что каждый вызов возвращает список, который был изменен, который создает новый список и отбрасывает возвращенный список как мусор? Есть ли способ обойти это с помощью деструктивных операций или это означает, что любая рекурсивная реализация будет тяжелой мусором? – asm

+0

Проверьте эти ответы: http://stackoverflow.com/questions/352203/generating-permutations-lazily –

+0

@Rainer Спасибо за дополнительную информацию! Я встроенный программист и, прежде всего, использую C, чтобы узнать, что мне нужно беспокоиться о том, когда вы используете язык GC'ed, мне действительно нужно работать. @Nathan Спасибо за отзыв, что выглядит действительно интересно. – asm

1

С точки зрения вывода, вы смотрите на слизь-repl, правильно?

Попробуйте перейти в буфер «* нижний-lisp *», вы, вероятно, увидите, что SBCL упал на ldb (встроенный низкоуровневый отладчик). Скорее всего, вам удалось взорвать стек вызовов.

+0

Нисходящий-lisp действительно упал в ldb. Похоже, мне пора узнать кое-что об этом. – asm

2

SBCL на большинстве платформ использует коллекцию сборщиков мусора, что означает, что выделенная память, которая выживает больше, чем некоторое количество коллекций, будет рассматриваться редко для сбора. Ваш алгоритм для данного тестового примера генерирует так много мусора, что он запускает GC столько раз, что фактические результаты, которые, очевидно, должны выдержать всю функциональную среду исполнения, сохраняются, то есть перемещаются в конечное поколение, которое собирается либо очень редко или совсем нет. Таким образом, второй запуск, при стандартных настройках для 32-битных систем, заканчивается из кучи (512 МБ, может быть увеличен с помощью параметров времени исполнения).

Данные по удержанию могут быть собраны с помощью ручного запуска коллектора с помощью (sb-ext:gc :full t). Это, очевидно, зависит от реализации.

 Смежные вопросы

  • Нет связанных вопросов^_^