Common Lisp позволяет динамически переопределять функции: если вы переопределили fn
в течение ок.Через 5 секунд вашего второго теста рабочий цикл переключится на вызов нового определения fn
во время работы.Эта функция имеет некоторые ограничения на то, как компилировать вызовы функций и как их оптимизировать, когда это необходимо.
Как указывает RainerJoswing в комментариях, приведенное выше является упрощением, есть случаи, когда компилятор может предполагатьфункции не переопределены (рекурсивные функции, функции в одном и том же файле), см. 3.2.2.3 Семантические ограничения , например:
Вызов в файле именованной функции, котораяопределяется в том же файле относится к этой функции, если только эта функция не была объявлена notinline .Последствия не указываются, если функции переопределяются индивидуально во время выполнения или многократно определены в одном и том же файле.
Функция смешивает проверку ошибок и вычисления, которые вы хотите выполнить.На границах вызова функции у вас обычно есть пролог, в котором проверяются ваши входные данные, и эпилог, где результаты могут быть «упакованы»: если компилятор знает, что локально переменная всегда является одинарным числом, он может использовать необработанное представление чисел во времяэкстент функции, но при возврате результата это должен быть допустимый тип Lisp, что означает приведение его к теговому значению, например.
Компилятор SBCL пытается обеспечить безопасность кода,где safe означает никогда не вызывать код с неопределенным поведением в спецификации Lisp.Однако обратите внимание, что если вы вызываете fn
со строковым вводом, ожидается, что код обнаружит ошибку типа.В отличие от C, ошибка типа во время выполнения в Лиспе четко определена (при условии, что тип , объявленный , который по умолчанию равен T, охватывает все возможные значения во время выполнения).Итак, компиляция кода на Лиспе для безопасности имеет тенденцию добавлять много проверок на ошибки в нескольких точках программы.
Оптимизация кода заключается в удалении проверок, которые гарантированно всегда будут истинными, устранении мертвых ветвей в сгенерированном коде.Например, если вы рассматриваете только fn
, вы можете видеть, что имеет для проверки своего ввода каждый раз, когда он вызывается, потому что он вполне может быть вызван с помощью строкового ввода.Но когда вы непосредственно включаете операцию, тогда индекс i
может быть статически определен как целое число, что позволяет применять вызовы =
и rem
без (значительной) проверки ошибок.
Оптимизация в SBCL происходит потому, что существует статический анализ, который отображает переменные на элементы решетки типов Lisp (and
и or
- это в основном наибольшая нижняя граница и нижняя верхняя граница для типов с типами T
и типом nil
на обоих концах).SBCL сообщает только об ошибках, которые обязательно произойдут: у вас есть ошибка, если вы вызываете функцию, которая принимает целые числа от 0 до 5, если вы вызываете ее с входом, который, как известно, всегда выше 5 или ниже нуля (оба набора не имеют пересечения), но у вас нет предупреждения, если вы вызываете его с целым числом от 2 до 10. Это безопасно, поскольку компилятор может отложить проверку ошибок во время выполнения, в отличие от других языков, где среда выполнения не имеет смысла типов (пытаясь предупреждать каждый раз, когдакод может иметь ошибки, что приведет к большому количеству предупреждений, учитывая открытость Lisp).
Вы можете (declaim (inline fn))
в своем файле, и тогда производительность будет идентична первойверсия.Практическое правило заключается в том, что внутри функции вещи немного более статичны, чем в глобальной среде: локальные функции не могут быть переопределены, у локальных переменных могут быть точно определены их типы и т. Д. У вас больше контроля над тем, что всегда верно.
Обратите внимание, что накладные расходы на проверку ошибок являются проблемой, если они выполняются много времени (относительно остальной части кода).Если вы заполняете большой массив одинарными числами и применяете к нему числовой код, имеет смысл использовать специализированный тип массива, такой как (simple-array single-float)
, или объявить локальные переменные как плавающие с (declare (type single-float x))
, так что вы не 't проверьте, что каждое значение фактически является float.В других случаях накладные расходы недостаточно высоки, чтобы тратить слишком много времени на их уменьшение.