如果没有缓存返回的常量,协议函数会更快地返回结果

时间:2016-09-30 22:33:39

标签: clojure benchmarking

我正在写Entity Component System。其中一部分是定义Systems如何使用的协议。协议的一部分是一个函数,它返回系统运行所需的组件。它可以归结为以下内容:

(defprotocol System
  (required-components [sys]
    "Returns a map of keys and initial values of components that the System requires to operate.")

因为这个函数返回的值实际上是一个常数,我认为缓存它可能是一个好主意,因为它可能需要每秒+60次。为了说明它是否有所作为,我写了以下测试:

(defrecord Input-System1 []
  System
  (required-components [sys] {:position [0 0] :angle 0}))

(def req-comps
  {:position [0 0] :angle 0})

(defrecord Input-System2 []
  System
  (required-components [sys] req-comps))

然后在REPL中,我进行了以下时间测试:

(let [sys (->Input-System1)]
  (time-multiple 1000000000
    (required-components sys)))

(let [sys (->Input-System2)]
  (time-multiple 1000000000
    (required-components sys)))

(下面的time-multiple代码)。

奇怪的是,Input-System1始终比Input-System2更快地完成:2789.973066ms与上次运行时的3800.345803ms相比。

我觉得这很奇怪,但从理论上讲,第1版是如何不断重新创建组件图,而版本2只引用预定义值。

我尝试通过削减协议重新创建:

(defn test-fn1 []
  req-comps)

(defn test-fn2 []
  {:position [0 0] :angle 0})

(time-multiple 1000000000
  (test-fn1))

(time-multiple 1000000000
  (test-fn2))

但这一次,结果几乎完全相同:3789.478675ms vs 3767.577814ms。

这让我相信它与协议有关,但我不知道是什么。这里发生了什么?我知道,考虑到测试次数,1000ms是相当微不足道的,所以我不打算在这里进行微优化。我只是好奇。

(defmacro time-pure
  "Evaluates expr and returns the time it took.
  Modified the native time macro to return the time taken."
  [expr]
  `(let [start# (current-nano-timestamp)
         ret# ~expr]
     (/ (double (- (current-nano-timestamp) start#)) 1000000.0)))


(defmacro time-multiple
  "Times the expression multiple times, returning the total time taken, in ms"
  [times expr]
  `(time-pure
     (dotimes [n# ~times]
      ~expr)))

1 个答案:

答案 0 :(得分:1)

在任何一种情况下,你的地图都是一个常量,在类加载过程中创建(它是静态已知的,因此在方法调用期间不会创建新对象。)另一方面,你的缓存情况会花费额外的间接费用 - 访问var。

演示:

(def req-comps
   {:position [0 0] :angle 0})

(defn asystem-1 []
   {:position [0 0] :angle 0})

(defn asystem-2 []
   req-comps)

(我们是否处理协议并不重要 - 函数编译相同,只需更简单地在编译代码中找到它们。)

public final class core$asystem_1 extends AFunction {
    public static final AFn const__4 = (AFn)RT.map(new Object[]{RT.keyword((String)null, "position"), Tuple.create(Long.valueOf(0L), Long.valueOf(0L)), RT.keyword((String)null, "angle"), Long.valueOf(0L)});

    public core$asystem_1() {
    }

    public static Object invokeStatic() {
        return const__4;
    }

    public Object invoke() {
        return invokeStatic();
    }
}

参见 - 它只返回预先计算的常数。

public final class core$asystem_2 extends AFunction {
    public static final Var const__0 = (Var)RT.var("so.core", "req-comps");

    public core$asystem_2() {
    }

    public static Object invokeStatic() {
        return const__0.getRawRoot();
    }

    public Object invoke() {
        return invokeStatic();
    }
}

额外拨打getRawRoot()