Clojure 为自定义序列重写'drop'
简而言之:在Clojure中,有没有一种方法可以从我编写的自定义序列类型上的标准序列API(在任何接口(如ISeq、IndexedSeq等)重新定义函数Clojure 为自定义序列重写'drop',clojure,overriding,Clojure,Overriding,简而言之:在Clojure中,有没有一种方法可以从我编写的自定义序列类型上的标准序列API(在任何接口(如ISeq、IndexedSeq等)重新定义函数 1.巨大的数据文件 我有以下格式的大文件: 包含条目数n的长(8字节) n条目,每个条目由3个长(即24字节)组成 2.自定义序列 我想对这些条目进行排序。由于我通常无法一次保存内存中的所有数据,并且我希望对其进行快速顺序访问,因此我编写了一个类似于以下内容的类: (deftype DataSeq [id
1.巨大的数据文件 我有以下格式的大文件:
- 包含条目数
的长(8字节)n
条目,每个条目由3个长(即24字节)组成n
(deftype DataSeq [id
^long cnt
^long i
cached-seq]
clojure.lang.IndexedSeq
(index [_] i)
(count [_] (- cnt i))
(seq [this] this)
(first [_] (first cached-seq))
(more [this] (if-let [s (next this)] s '()))
(next [_] (if (not= (inc i) cnt)
(if (next cached-seq)
(DataSeq. id cnt (inc i) (next cached-seq))
(DataSeq. id cnt (inc i)
(with-open [f (open-data-file id)]
; open a memory mapped byte array on the file
; seek to the exact position to begin reading
; decide on an optimal amount of data to read
; eagerly read and return that amount of data
))))))
其主要思想是预先读取列表中的一组条目,然后从该列表中消费。每当缓存被完全消耗时,如果还有剩余的条目,就会从新缓存列表中的文件中读取它们。就这么简单
为了创建这样一个序列的实例,我使用了一个非常简单的函数,如:
(defn ^DataSeq load-data [id]
(next (DataSeq. id (count-entries id) -1 [])))
; count-entries is a trivial "open file and read a long" memoized
如您所见,数据的格式允许我以非常简单和高效的方式实现count
3. <代码>下降可以是O(1)
本着同样的精神,我想重新实现删除
。这些数据文件的格式允许我在O(1)(而不是标准的O(n))中重新实现drop
,如下所示:
- 如果删除的内容少于剩余的缓存项,只需从缓存中删除相同数量的内容即可
- 如果删除的内容超过cnt,则只需返回空列表即可
- 否则,只需找出数据文件中的位置,直接跳到该位置,然后从那里读取数据
drop
的实现方式与count
、first
、seq
等不同。后一个函数在RT
中调用一个类似名称的静态方法,该方法反过来调用我上面的实现,而前一个函数,drop
,不检查调用它的序列的实例是否提供自定义实现
很明显,我可以提供一个名为“除了”drop“之外的任何函数,它完全满足我的需求,但这会迫使其他人(包括我未来的自己)记住使用它,而不是每次都使用它,这很糟糕
因此,问题是:是否可以覆盖drop
的默认行为?
4.变通办法(我不喜欢)
在写这个问题的时候,我刚刚想出了一个可能的解决办法:让阅读变得更懒。自定义序列只保留一个索引并推迟读取操作,这只有在调用第一个时才会发生。问题是我需要一些可变状态:对first
的第一次调用会导致一些数据被读入缓存,所有后续调用都会从该缓存返回数据。在next
上也会有类似的逻辑:如果有缓存,只需next
就可以了;否则,不要费心填充它——它将在再次调用first
时完成
这将避免不必要的磁盘读取。然而,这仍然不是最优的——它仍然是O(n),很容易就是O(1)
不管怎样,我不喜欢这种变通方法,我的问题仍然悬而未决。有什么想法吗
谢谢。目前,我实施了上述解决方法。它的工作原理是将读取延迟到对(first)
的第一次调用,该调用将数据存储在本地可变缓存中
请注意,此版本使用了非同步可变表
(以避免每次调用第一个
、下一个
和更多
,以及第一次调用第一个
时的易失性写入)。换句话说:不要在线程之间共享。要使其线程安全,请改用volatile mutable
(这会导致较小的性能损失)。它仍然可能导致不同线程多次读取相同的数据。为了避免这种情况,请切换回非同步可变
,并确保在读取或写入字段缓存时使用(锁定此…
)
编辑:经过一些(非严格的)测试后,(锁定此…
)引入的开销似乎与从磁盘进行不必要的读取所引入的开销类似(请注意,我正在从快速SSD读取,该SSD可能已经缓存了部分数据)。因此,目前最好的线程安全解决方案(以及我的特定硬件)是使用易失性缓存
(deftype DataSeq [id
^long cnt
^long i
^{:unsynchronized-mutable true} cache]
clojure.lang.IndexedSeq
(index [_] i)
(count [_] (- cnt i))
(seq [this] this)
(more [this] (if-let [s (.next this)] s '()))
(next [_] (if (not= (inc i) cnt)
(DataSeq. id cnt (inc i) (next cache))))
(first [_]
(when-not (seq cache)
(set! cache
(with-open [f (open-data-file id)]
; open a memory mapped byte array on the file
; seek to the exact position to begin reading
; decide on an optimal amount of data to read
; eagerly read and return that amount of data
)))
(first cache)))
仍然困扰我的是,我必须使用可变状态来阻止从磁盘读取删除(即,“滚出,你这个无用的数据块”…目前,我实现了上述解决方法。它的工作原理是将读取延迟到对(first)
的第一次调用,该调用将数据存储在本地可变缓存中
请注意,此版本使用了非同步可变表
(以避免每次调用第一个
、下一个
和更多
,以及第一次调用第一个
时的易失性写入)。换句话说:不要在线程之间共享。要使其线程安全,请改用volatile mutable
(这会导致较小的性能损失)。它仍然可能导致不同线程多次读取相同的数据。为了避免这种情况,请切换回非同步可变
,并确保在读取或写入字段缓存时使用(锁定此…
)
编辑:经过一些(非严格的)测试后,(锁定此…
引入的开销似乎与从磁盘进行不必要的读取所引入的开销相似(请注意,我是从快速SSD读取,即