【发布时间】:2015-02-19 11:22:09
【问题描述】:
根据我目前使用 Racket 的经验,我没有过多考虑向量,因为我发现它们的主要好处——对元素的恒定时间访问——在你使用大量元素之前并不重要。
但是,这似乎不太准确。即使元素数量很少,向量也具有性能优势。例如,分配一个列表比分配一个向量要慢:
#lang racket
(time (for ([i (in-range 1000000)]) (make-list 50 #t)))
(time (for ([i (in-range 1000000)]) (make-vector 50 #t)))
>cpu time: 1337 real time: 1346 gc time: 987
>cpu time: 123 real time: 124 gc time: 39
而且检索元素也更慢:
#lang racket
(define l (range 50))
(define v (make-vector 50 0))
(time (for ([i (in-range 1000000)]) (list-ref l 49)))
(time (for ([i (in-range 1000000)]) (vector-ref v 49)))
>cpu time: 77 real time: 76 gc time: 0
>cpu time: 15 real time: 15 gc time: 0
顺便说一句,如果我们增加到 1000 万,这个性能比仍然成立:
#lang racket
(define l (range 50))
(define v (make-vector 50 0))
(time (for ([i (in-range 10000000)]) (list-ref l 49)))
(time (for ([i (in-range 10000000)]) (vector-ref v 49)))
>cpu time: 710 real time: 709 gc time: 0
>cpu time: 116 real time: 116 gc time: 0
当然,这些是综合示例,大多数程序不会在循环中分配结构或使用list-ref 一百万次。 (是的,我故意抓住第 50 个元素来说明性能差异。)
但它们也不是,因为在依赖列表的整个程序中,每次触摸这些列表时都会产生一些额外的开销,所有这些低效率的小问题都会加起来导致运行时间变慢为整个计划。
因此我的问题是:为什么不一直使用向量?在什么情况下,我们应该期望列表有更好的性能?
我最好的猜测是因为从列表的前面中取出一个项目同样快,例如:
#lang racket
(define l (range 50))
(define v (make-vector 50 0))
(time (for ([i (in-range 1000000)]) (list-ref l 0)))
(time (for ([i (in-range 1000000)]) (vector-ref v 0)))
>cpu time: 15 real time: 16 gc time: 0
>cpu time: 12 real time: 11 gc time: 0
...列表在递归情况下是首选,因为您主要使用cons 和car 和cdr,并且它节省了使用列表的空间(向量不能被破坏并放回一起而不复制整个向量,对吧?)
但在您存储和检索数据元素的情况下,无论长度如何,向量似乎都有优势。
【问题讨论】:
-
我认为我不介意使用
list-ref。查找不是线性的。 -
何时使用(从更一般的意义上)数组与链表?
-
我很确定,尽管这是一个 C++ 视频,但它在这里解释了问题:youtube.com/watch?v=YQs6IC-vgmo
-
请注意,长度也需要线性时间,因此如果您想单独测量 list-ref,请将 (length l) 移到 for 循环之外。
-
@MatthewButterick:在 Lisp 和 Scheme 中,列表只是一个单链表。我想不出 Lisp 或 Scheme 比任何其他语言有什么实质性的好处。我知道 Clojure 做事不同。我怀疑那里的差异会比传统实现小得多。
标签: list data-structures scheme racket