Динамично генерирующая высокая производительность функционирует в clojure

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

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

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

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

(compile-spec my-spec)

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

Что лучший способ состоит в том, чтобы сделать это в Clojure?

7
задан mikera 13 May 2010 в 15:53
поделиться

2 ответа

Хамза Ерликая уже отметил самое важное, что код 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

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

11
ответ дан 6 December 2019 в 11:46
поделиться

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

6
ответ дан 6 December 2019 в 11:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: