简而言之:在Clojure中,有没有办法在我写的自定义序列类型上从标准序列API(在ISeq,IndexedSeq等任何接口上没有定义)重新定义函数?
我有以下格式的大文件:
n
的长(8个字节)n
个条目,每个条目由3个长整数(即24个字节)组成我希望这些条目有一个序列。由于我通常不能同时将所有数据保存在内存中,并且我希望快速顺序访问它,我写了一个类似于以下的类:
(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
。
drop
可以是O(1)本着同样的精神,我想重新实现drop
。这些数据文件的格式允许我重新实现O(1)中的drop
(而不是标准O(n)),如下所示:
如果减少剩余的缓存项目,只需从缓存中删除相同的金额并完成;
如果丢弃超过cnt
,则只返回空列表。
否则,只需找出数据文件中的位置,向右跳到该位置,然后从那里读取数据。
我的难点在于,drop
的实现方式与count
,first
,seq
等不同。后一种函数调用类似命名的静态方法RT
反过来调用我上面的实现,而前者drop
不会检查调用它的序列的实例是否提供自定义实现。
显然,我可以提供一个名为drop
的函数,它完全符合我的要求,但这会强迫其他人(包括我未来的自己)记住每次使用它而不是drop
单次,这很糟糕。
drop
的默认行为? 在撰写这个问题时,我只想出了一个可能的解决方法:让阅读变得更加懒散。自定义序列只保留一个索引并推迟读取操作,这只有在调用first
时才会发生。问题是我需要一些可变状态:第一次调用first
会导致一些数据被读入缓存,所有后续调用都将从此缓存返回数据。 next
上会有类似的逻辑:如果有缓存,只需next
;否则,不要费心填充它 - 将在再次调用first
时完成。
这可以避免不必要的磁盘读取。然而,这仍然不是最优的 - 它仍然是O(n),它很容易就是O(1)。
无论如何,我不喜欢这种解决方法,我的问题仍然存在。有什么想法吗?
感谢。
答案 0 :(得分:1)
目前,我实施了上述的解决方法。它的工作原理是将读数推迟到(first)
的第一次调用,该调用将数据存储在本地可变缓存中。
请注意,此版本使用unsynchronized-mutable
(以避免每次调用first
,next
和more
时出现易失性读取-write第一次调用first
)。换句话说:不要在线之间分享。为了使其成为线程安全的,请改用volatile-mutable
(这会导致性能损失很小)。它仍然可能导致不同线程对同一数据进行多次读取。为避免这种情况,请更改回unsynchronized-mutable
,并确保在阅读或写入字段(locking this ...)
时使用cache
。
编辑:经过一些(非严格的)测试后,(locking this ...)
引入的开销似乎与磁盘上不必要的读取引入的开销类似(请注意,我正在阅读来自快速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)))
还有什么困扰我的是,我必须使用可变状态来阻止drop
(即,“脱离,你无用的数据”)从磁盘读取......