Динамическое создание высокопроизводительных функций в clojure - PullRequest
7 голосов
/ 13 мая 2010

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

например. предположим, что я указываю функции с простым DSL, например:

(def my-spec [:add [:multiply 2 :param0] 3])

Я бы хотел создать спецификацию компиляции функции, такую:

(compile-spec my-spec)

Возвращает скомпилированную функцию с одним параметром x, которая возвращает 2x + 3.

Каков наилучший способ сделать это в Clojure?

Ответы [ 2 ]

11 голосов
/ 13 мая 2010

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

Во-первых, вышеприведенный пункт о том, что код Clojure всегда компилируется, включает в себя замыкания, возвращаемые функциями и функциями высшего порядка, созданными путем вызова eval в fn / fn* формах, и действительно всего, что может действовать как функция Clojure. , Таким образом, вам не нужен отдельный DSL для описания функций, просто используйте функции более высокого порядка (и, возможно, макросы):

(defn make-affine-function [a b]
  (fn [x] (+ (* a x) b)))

((make-affine-function 31 47) 5)
; => 202

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

(defmacro make-primitive-affine-function [t a b]
  (let [cast #(list (symbol (name t)) %)
        x (gensym "x")]
    `(fn [~x] (+ (* ~(cast a) ~(cast x)) ~(cast b)))))

((make-primitive-affine-function :int 31 47) 5)
; => 202

Используйте :int, :long, :float или :double (или символы, не соответствующие пространству имен соответствующих имен) в качестве первого аргумента, чтобы воспользоваться преимуществами неупакованной примитивной арифметики, подходящей для ваших типов аргументов. В зависимости от того, что делает ваша функция, это может значительно повысить производительность.

Другие типы подсказок обычно предоставляются с синтаксисом #^Foo bar (^Foo bar делает то же самое в 1.2); если вы хотите добавить их в код, сгенерированный макросом, исследуйте функцию with-meta (вам нужно объединить '{:tag Foo} в метаданные символов, представляющих формальные аргументы для ваших функций или let введенных вами локальных объектов, которые вы хочу поставить подсказки типа).


О, и если вы все еще хотите знать, как реализовать свою оригинальную идею ...

Вы всегда можете создать выражение Clojure, чтобы определить свою функцию - (list 'fn ['x] (a-magic-function-to-generate-some-code some-args ...)) - и вызвать eval для результата. Это позволило бы вам сделать что-то вроде следующего (было бы проще потребовать, чтобы спецификация включала список параметров, но вот версия, предполагающая, что аргументы должны быть вычеркнуты из спецификации, все они называются paramFOO и должны быть лексикографически отсортировано):

(require '[clojure.walk :as walk])

(defn compile-spec [spec]
  (let [params (atom #{})]
    (walk/prewalk
     (fn [item]
       (if (and (symbol? item) (.startsWith (name item) "param"))
         (do (swap! params conj item)
             item)
         item))
     spec)
    (eval `(fn [~@(sort @params)] ~@spec))))

(def my-spec '[(+ (* 31 param0) 47)])

((compile-spec my-spec) 5)
; => 202

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

6 голосов
/ 13 мая 2010

Даже если вы не AOT скомпилировали свой код, как только вы определите функцию, она будет скомпилирована в байт-код на лету.

...