在 Racket 中使用序列长度、序列引用、序列映射等而不是列表(长度列表引用等)、字符串(字符串长度、字符串引用等)、向量等的不同函数有什么缺点吗?
表现。
\n\n考虑这个微小的基准:
\n\n#lang racket/base\n\n(require racket/sequence)\n\n(define len 10000)\n(define vec (make-vector len))\n\n(collect-garbage)\n(collect-garbage)\n(collect-garbage)\n\n(time (void (for/list ([i (in-range len)])\n (vector-ref vec i))))\n\n(collect-garbage)\n(collect-garbage)\n(collect-garbage)\n\n(time (void (for/list ([i (in-range len)])\n (sequence-ref vec i))))\n
Run Code Online (Sandbox Code Playgroud)\n\n这是我机器上的输出:
\n\n; vectors (vector-ref vs sequence-ref)\ncpu time: 1 real time: 1 gc time: 0\ncpu time: 2082 real time: 2081 gc time: 0\n
Run Code Online (Sandbox Code Playgroud)\n\n是的,那\xe2\x80\x99相差了3个数量级。
\n\n为什么?好吧,racket/sequence
这不是一个可怕的 \xe2\x80\x9csmart\xe2\x80\x9d API,即使向量是随机访问,sequence-ref
也不是。结合 Racket 优化器大量优化原始操作的能力,序列 API 是一个相当糟糕的接口。
当然,这有点不公平,因为向量是随机访问的,而列表之类的东西则不是。然而,执行与上面完全相同的测试但使用列表而不是向量仍然会产生相当严峻的结果:
\n\n; lists (list-ref vs sequence-ref)\ncpu time: 113 real time: 113 gc time: 0\ncpu time: 1733 real time: 1732 gc time: 0\n
Run Code Online (Sandbox Code Playgroud)\n\n序列 API 很慢,主要是因为高级别的间接性。
\n\n现在,仅仅性能并不是完全拒绝 API 的理由,因为在更高的抽象级别上工作有具体的优势。也就是说,我认为序列 API 不是一个好的抽象,因为它:
\n\n\xe2\x80\xa6 在其实现中不必要地有状态,这给接口的实现者带来了不必要的负担。
\xe2\x80\xa6 不容纳与列表不同的内容,例如随机访问向量或哈希表。
如果您想使用更高级别的 API,一个可能的选择是使用 package collections
,它试图提供类似于 的 API racket/sequence
,但容纳更多种类的数据结构,并且还具有更完整的功能集。免责声明:我是该包的作者collections
。
再次给出上述基准,性能仍然比直接使用底层函数差,但它\xe2\x80\x99s至少更易于管理:
\n\n; vectors (vector-ref vs ref)\ncpu time: 2 real time: 1 gc time: 0\ncpu time: 97 real time: 98 gc time: 10\n\n; lists (list-ref vs ref)\ncpu time: 104 real time: 103 gc time: 0\ncpu time: 481 real time: 482 gc time: 0\n
Run Code Online (Sandbox Code Playgroud)\n\n您是否能负担得起开销取决于您到底在做什么,并且由您自己决定。只要执行某种动态调度,专用操作就总是比遵循它们的操作至少快一些。一如既往,记住性能优化的规则:don\xe2\x80\x99t 猜测,测量。
\n