为什么 TIME 报告的字节数因调用而异?

Why are the number of bytes consed reported by TIME differing for different calls?

使用 SBCL 1.4.12,我正在查看 Stuart Shapiro 的 Common Lisp:交互式方法 中的练习 17.9,并计时 reverse 函数应用于列表10,000 个元素。当我使用同一个列表计时这个函数时,time 函数每次报告不同的字节数。

这里是 reverse 函数的代码:

(defun reverse2 (l1 l2)
  "Returns a list consisting of the members of L1 in reverse order
   followed by the members of L2 in original order."
  (check-type l1 list)
  (check-type l2 list)
  (if (endp l1) l2
      (reverse2 (rest l1)
                (cons (first l1) l2))))

(defun reverse1 (l)
  "Returns a copy of the list L1
   with the order of members reversed."
  (check-type l list)
  (reverse2 l '()))

我在 REPL 中生成了列表:

(defvar *test-list* '())
(dotimes (x 10000)
  (setf *test-list* (cons x *test-list*)))

以下是四次测试运行的结果:

CL-USER> (time (ch17:reverse1 *test-list*))
Evaluation took:
  0.000 seconds of real time
  0.000000 seconds of total run time (0.000000 user, 0.000000 system)
  100.00% CPU
  520,386 processor cycles
  145,696 bytes consed

CL-USER> (time (ch17:reverse1 *test-list*))
Evaluation took:
  0.000 seconds of real time
  0.000000 seconds of total run time (0.000000 user, 0.000000 system)
  100.00% CPU
  260,640 processor cycles
  178,416 bytes consed

CL-USER> (time (ch17:reverse1 *test-list*))
Evaluation took:
  0.000 seconds of real time
  0.000000 seconds of total run time (0.000000 user, 0.000000 system)
  100.00% CPU
  279,822 processor cycles
  178,416 bytes consed

CL-USER> (time (ch17:reverse1 *test-list*))
Evaluation took:
  0.000 seconds of real time
  0.000000 seconds of total run time (0.000000 user, 0.000000 system)
  100.00% CPU
  264,700 processor cycles
  161,504 bytes consed

第二次和第三次测试运行(相隔几分钟)显示相同的字节数,但另外两次显示不同的数字。我预计时间会有所不同,但我没想到字节数会有所不同。我看到 HyperSpec 对 time 函数说:

In general, these timings are not guaranteed to be reliable enough for marketing comparisons. Their value is primarily heuristic, for tuning purposes.

但我预计这适用于计时,而不适用于字节数。 time 报告的 bytes consed 值是否不可靠?幕后是否有对此负责的优化?我错过了什么?

coning 的数量(在 'bytes of memory allocated' 意义上)取决于一切:

  • 这取决于你分配了多少个什么类型的对象;
  • 这取决于分配器的精细实现细节,例如是否以大块分配以及是否记录大块分配之间的'allocation';
  • 这取决于垃圾收集器——是否触发了垃圾收集器?如果有的话是哪一种? GC有多毛? GC本身会分配吗?如何跨 GC 计算分配?
  • 这取决于系统是否正在进行其他分配,例如在其他线程中,以及该分配是否在您的线程中被计算在内——是只有一个分配器还是每个线程都有分配器?
  • 这取决于月相和冥王星是否是行星;
  • 等等。

一般来说,如果您有一个非常简单的单线程实现以及一个非常简单的分配器和一个非常简单的 GC,那么跟踪分配就很容易,您将获得可靠的数字。许多 Lisp 实现曾经是这样的:它们很容易理解,当它们做任何事情时你都得喝很多茶(好吧,那时机器比较慢,但即使按照当时的标准,它们的速度也常常令人印象深刻)。现在 Lisps 有多个线程、复杂的分配器和 GC,它们确实很快,但是发生了多少分配已经成为一个很难回答并且常常有点不可预测的问题。