如果不缓存常量 return 协议函数 return 结果更快
Protocol functions return a result faster if the constant it returns isn't cached
我正在写 Entity Component System。它的一部分是定义如何使用 Systems
的协议。协议的一部分是 returns 系统运行所需组件的功能。它可以提炼为以下内容:
(defprotocol System
(required-components [sys]
"Returns a map of keys and initial values of components that the System requires to operate.")
因为这个函数 returns 的值实际上是一个常量,我认为缓存它可能是个好主意,因为它可能需要每秒 +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.973066 毫秒对最后一个 运行 的 3800.345803 毫秒。
我觉得这很奇怪,尽管从理论上讲,版本 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。
这让我相信它与协议有关,但我不能说是什么。这里发生了什么?我知道考虑到测试的数量,1000 毫秒是微不足道的,所以我不想在这里进行微优化。我只是好奇。
(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)))
在任何一种情况下,您的地图都是一个常量,在 class 加载期间创建(它是静态已知的,因此在方法调用期间不会创建新对象。)另一方面,您的缓存案例会花费您额外的费用间接 - 访问 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();
}
}
看 - 它只是 returns 预先计算的常量。
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()
。
我正在写 Entity Component System。它的一部分是定义如何使用 Systems
的协议。协议的一部分是 returns 系统运行所需组件的功能。它可以提炼为以下内容:
(defprotocol System
(required-components [sys]
"Returns a map of keys and initial values of components that the System requires to operate.")
因为这个函数 returns 的值实际上是一个常量,我认为缓存它可能是个好主意,因为它可能需要每秒 +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.973066 毫秒对最后一个 运行 的 3800.345803 毫秒。
我觉得这很奇怪,尽管从理论上讲,版本 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。
这让我相信它与协议有关,但我不能说是什么。这里发生了什么?我知道考虑到测试的数量,1000 毫秒是微不足道的,所以我不想在这里进行微优化。我只是好奇。
(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)))
在任何一种情况下,您的地图都是一个常量,在 class 加载期间创建(它是静态已知的,因此在方法调用期间不会创建新对象。)另一方面,您的缓存案例会花费您额外的费用间接 - 访问 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();
}
}
看 - 它只是 returns 预先计算的常量。
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()
。