在clojure中动态生成高性能函数

时间:2010-05-13 15:53:54

标签: java compiler-construction dynamic code-generation clojure

我正在尝试使用Clojure来动态生成可应用于大量数据的函数 - 即要求是将函数编译为字节码以便快速执行,但是直到运行时才能知道它们的规范

e.g。假设我使用简单的DSL指定函数,如:

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

我想创建一个函数compile-spec,以便:

(compile-spec my-spec)

将返回一个返回2x + 3的参数x的编译函数。

在Clojure中执行此操作的最佳方法是什么?

2 个答案:

答案 0 :(得分:11)

Hamza Yerlikaya已经提出了最重要的观点,即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}合并到表示函数的正式参数的符号的元数据中,或{{1} } - 介绍你想要输入类型提示的本地人。)


哦,如果你还想知道如何实现你原来的想法......

您始终可以构造Clojure表达式来定义您的函数 - let - 并在结果上调用(list 'fn ['x] (a-magic-function-to-generate-some-code some-args ...))。这将使您能够执行以下操作(要求规范包含参数列表会更简单,但这里是假设参数从规范中删除的版本,都称为eval并且是按字典顺序排序):

paramFOO

绝大多数时候,没有充分理由以这种方式做事,应该避免;使用高阶函数和宏代替。但是,如果你正在做类似于进化编程的事情,那么它就在那里,提供最大的灵活性 - 结果仍然是编译功能。

答案 1 :(得分:6)

即使您没有AOT编译代码,只要您定义一个函数,它就会被动态编译为字节码。