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