【问题标题】:Dynamically generating high performance functions in clojure在 clojure 中动态生成高性能函数
【发布时间】:2011-02-19 04:07:07
【问题描述】:

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

例如假设我使用简单的 DSL 指定函数,例如:

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

我想创建一个函数 compile-spec 这样:

(compile-spec my-spec)

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

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

【问题讨论】:

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


    【解决方案1】:

    Hamza Yerlikaya 已经提出了最重要的一点,那就是 Clojure 代码是总是编译的。我只是为您的优化工作添加一个插图和一些关于一些容易实现的目标的信息。

    首先,关于 Clojure 代码始终被编译的上述观点包括由高阶函数返回的闭包和通过在 fn / fn* 表单上调用 eval 创建的函数以及实际上任何其他可以充当 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-introduced locals 您希望添加类型提示)。


    哦,如果你还想知道如何实现你最初的想法......

    您始终可以构造 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
    

    绝大多数时候,没有充分的理由这样做,应该避免;改用高阶函数和宏。但是,如果您正在做类似进化编程之类的事情,那么它就在那里,提供了最大的灵活性——结果仍然是一个编译函数。

    【讨论】:

    • 这是一个很好的答案:帮助我了解幕后发生的事情并完美解决问题。非常感谢米哈尔!
    【解决方案2】:

    即使您不 AOT 编译您的代码,只要您定义了一个函数,它就会立即编译为字节码。

    【讨论】:

      猜你喜欢
      • 2011-12-12
      • 1970-01-01
      • 2018-03-06
      • 1970-01-01
      • 2011-09-11
      • 1970-01-01
      • 2011-11-08
      • 1970-01-01
      相关资源
      最近更新 更多