有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

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

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

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

我想创建一个函数编译规范,以便:

(compile-spec my-spec)

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

在Clojure做这件事的最佳方式是什么


共 (2) 个答案

  1. # 1 楼答案

    即使你没有编译你的代码,一旦你定义了一个函数,它就会被编译成字节码

  2. # 2 楼答案

    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引入的局部变量中,以便对其进行类型提示)


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

    您总是可以构造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
    

    绝大多数情况下,这样做没有充分的理由,应该避免;改用高阶函数和宏。然而,如果你正在做一些事情,比如说,进化编程,那么它就在那里,提供了终极的灵活性,结果仍然是一个编译函数