我使用这个基本宏作为其他计时宏的构建块:
(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-each [& exprs]
`(mapv #(time-pure %) '~exprs))
我希望这会将每个表达式赋予time-each
,在其中执行并计时;返回结果。然而,当我测试它时,它立即完成:
(time-each
(Thread/sleep 500)
(Thread/sleep 1000))
[0.036571 0.0]
我对此感到困惑,因为我知道(time-pure (Thread/sleep 1000))
将花费大约一秒的时间来返回,并且所有这个宏都会委托给time-pure
。
造成这种情况的原因是什么?我真的不知道如何正确调试宏。我使用macro-expand-1
来检查生成的代码:
(clojure.pprint/pprint
(macroexpand-1
'(time-each
(Thread/sleep 500)
(Thread/sleep 1000))))
(clojure.core/mapv
(fn*
[p1__1451__1452__auto__]
(helpers.general-helpers/time-pure p1__1451__1452__auto__))
'((Thread/sleep 500) (Thread/sleep 1000)))
但没有什么能让我真正脱颖而出。
这里发生了什么?
(注意,这是我几分钟前发布的一个问题。我意识到我展示的案例很复杂,所以我做了一个更好的例子。)
答案 0 :(得分:2)
<强>解决方案强>
我认为这里发生的是mapv
在编译时执行,这意味着在运行时,这就是为什么它不能将代码列表“粘贴”为sexp。我认为更好的方法是将mapv
保留在语法引用之外:
(defmacro each-time-mine [& exprs]
(mapv #(time-pure %) exprs))
(each-time-mine
(Thread/sleep 500)
(Thread/sleep 1000))
[501.580465 1001.196752]
<强>原始强>
虽然eval
通常不受欢迎,但在这种情况下似乎可以解决问题:
(defmacro time-pure
[expr]
`(let [start# (current-nano-timestamp)
ret# (eval ~expr)]
(/ (double (- (current-nano-timestamp) start#)) 1000000.0)))
(defmacro time-each [& exprs]
`(mapv #(time-pure %) '~exprs))
(time-each
(Thread/sleep 500)
(Thread/sleep 1000))
[501.249249 1001.242522]
mapv
会将性别视为列表,并且time-pure
想要执行它们时,它只会为列表的值分配ret#
。所以该列表需要eval
。
虽然可能有更好的方法来实现这一目标。
答案 1 :(得分:1)
(defmacro time-each [& exprs]
`(list ~@(for [expr exprs]
`(time-pure ~expr))))
你需要注意不要评估time-pure
上下文之外的表达式,就像在Arthur的答案中所做的那样,它会评估每个表达式,然后在(非常)上调用time-pure
快速运行&#34;查看结果&#34;。相反,在评估它之前,在每个表达式周围包裹time-pure
。
答案 2 :(得分:0)
“我仍然试图不要围绕宏。”
需要一段时间。 :)帮助我的一件事是宏观扩张:
(macroexpand-1 '(time-each (+ 2 2 )(+ 3 3)))
产生:
(clojure.core/mapv
(fn* [p1__24013__24014__auto__]
(rieclj.core/time-pure p1__24013__24014__auto__))
(quote ((+ 2 2) (+ 3 3))))
令人震惊的是,时间纯粹是通过引用列表,所以它只是运行时的符号,而不是宏扩展时间。
然后另一件帮助我的事情是Lispy宏的美妙之处在于它们是在不同时间运行的正常函数(当读者正在处理源代码时)。但是因为它们是正常函数,所以我们可以使用print语句探测它们,就像我们探测普通代码一样。
我修改了time-pure来打印出它的macroexpansion-time参数expr,并在生成代码中打印出已评估的输入,我们现在怀疑这是一个符号列表,或'(+ 2 2) )在第一种情况下。
(defmacro time-pure
"Evaluates expr and returns the time it took.
Modified the native time macro to return the time taken."
[expr]
(prn :time-pure-sees expr)
`(let [start# (now)
ret# ~expr]
(prn :ret-is ret# (type (first ret#)))
(/ (double (- (now) start#)) 1000000.0)))
我打印第一个ret的类型以驱动回家+是符号,而不是函数。评价:
(time-each (+ 2 2 )(+ 3 3))
的产率:
:time-pure-sees p1__24013__24014__auto__
:ret-is (+ 2 2) clojure.lang.Symbol
:ret-is (+ 3 3) clojure.lang.Symbol
看到(+ 2 2)可能会让你觉得一切都很好,但关键是右边是'(+ 2 2)所以ret#绑定到那个符号表达而不是预期的计算。
同样,打印第一种类型希望能够清楚地表明,时间纯粹是在运行时处理符号列表。
很抱歉,如果所有这些都令人困惑,但道德很简单:当宏与你的脑袋混乱时,使用macroexpand-1和嵌入式打印语句。随着时间的推移,你将内化两个不同的时间,宏扩展和运行。