Clojure 为自定义序列重写'drop'

Clojure 为自定义序列重写'drop',clojure,overriding,Clojure,Overriding,简而言之:在Clojure中,有没有一种方法可以从我编写的自定义序列类型上的标准序列API(在任何接口(如ISeq、IndexedSeq等)重新定义函数 1.巨大的数据文件 我有以下格式的大文件: 包含条目数n的长(8字节) n条目,每个条目由3个长(即24字节)组成 2.自定义序列 我想对这些条目进行排序。由于我通常无法一次保存内存中的所有数据,并且我希望对其进行快速顺序访问,因此我编写了一个类似于以下内容的类: (deftype DataSeq [id

简而言之:在Clojure中,有没有一种方法可以从我编写的自定义序列类型上的标准序列API(在任何接口(如ISeq、IndexedSeq等)重新定义函数


1.巨大的数据文件 我有以下格式的大文件:

  • 包含条目数
    n
    的长(8字节)
  • n
    条目,每个条目由3个长(即24字节)组成
2.自定义序列 我想对这些条目进行排序。由于我通常无法一次保存内存中的所有数据,并且我希望对其进行快速顺序访问,因此我编写了一个类似于以下内容的类:

(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读取,即